機能リリースフラグの導入に伴い、遵守すべき新しいテストポリシーが設けられました。
- テストでは、フラグが有効な場合と無効な場合の両方の動作をカバーする必要があります。
- テスト中にフラグの値を設定するには、公式メカニズムを使用する必要があります。
- xTS テストでは、テスト内のフラグ値をオーバーライドしないでください。
次のセクションでは、これらのポリシーを遵守するために使用すべき公式メカニズムについて説明します。
フラグが付けられたコードをテストする
テストシナリオ | 使用するメカニズム |
---|---|
フラグ値が頻繁に変更される場合のローカルテスト | Android Debug Bridge(実行時にフラグの値を変更するで説明) |
フラグ値が頻繁に変更されない場合のローカルテスト | フラグ値ファイル(機能リリースフラグの値を設定するで説明) |
フラグ値が変更されるエンドツーエンド テスト | FeatureFlagTargetPreparer (エンドツーエンド テストを作成するで説明) |
フラグ値が変更される単体テスト | @EnableFlags と @DisableFlags を使用した SetFlagsRule (単体テストを作成する(Java および Kotlin)または単体テストを作成する(C および C++)で説明) |
フラグ値を変更できないエンドツーエンド テストまたは単体テスト | CheckFlagsRule (フラグ値が変更されないエンドツーエンド テストまたは単体テストを作成するで説明) |
エンドツーエンド テストを作成する
AOSP には、デバイス上でのエンドツーエンド テストを可能にする FeatureFlagTargetPreparer
というクラスが用意されています。このクラスは、フラグ値のオーバーライドを入力として受け取り、テスト実行前にデバイス設定でそれらのフラグを設定し、実行後にフラグを復元します。
FeatureFlagTargetPreparer
クラスの機能は、テスト モジュール レベルとテスト構成レベルで適用できます。
テスト モジュール設定で FeatureFlagTargetPreparer を適用する
テスト モジュール設定で FeatureFlagTargetPreparer
を適用するには、FeatureFlagTargetPreparer
とフラグ値のオーバーライドを AndroidTest.xml
テスト モジュール構成ファイルに含めます。
<target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer">
<option name="flag-value"
value="permissions/com.android.permission.flags.device_aware_permission_grant=true"/>
<option name="flag-value"
value="virtual_devices/android.companion.virtual.flags.stream_permissions=true"/>
</target_preparer>
ここで、
target.preparer class
は常にcom.android.tradefed.targetprep.FeatureFlagTargetPreparer
に設定されます。option
がフラグのオーバーライドで、name
は常にflag-value
に設定され、value
はnamespace/aconfigPackage.flagName=true|false
に設定されます。
フラグの状態に基づいてパラメータ化されたテスト モジュールを作成する
フラグの状態に基づいてパラメータ化されたテスト モジュールを作成するには:
FeatureFlagTargetPreparer
をAndroidTest.xml
テスト モジュール構成ファイルに含めます。<target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
Android.bp
ビルドファイルのtest_module_config
セクションで、フラグ値のオプションを指定します。android_test { name: "MyTest" ... } test_module_config { name: "MyTestWithMyFlagEnabled", base: "MyTest", ... options: [ {name: "flag-value", value: "telephony/com.android.internal.telephony.flags.oem_enabled_satellite_flag=true"}, ], } test_module_config { name: "MyTestWithMyFlagDisabled", base: "MyTest", ... options: [ {name: "flag-value", value: "telephony/com.android.internal.telephony.flags.carrier_enabled_satellite_flag=true"}, ], }
options
フィールドには、フラグのオーバーライドが含まれます。name
は常にflag-value
に設定され、value
はnamespace/aconfigPackage.flagName=true|false
に設定されます。
単体テストを作成する(Java および Kotlin)
このセクションでは、Java および Kotlin のテストにおいて、クラスレベルとメソッドレベル(テストごと)で aconfig フラグ値をオーバーライドする方法について説明します。
フラグが多数ある大規模なコードベースで自動単体テストを作成するには、次の手順を行います。
@EnableFlags
アノテーションと@DisableFlags
アノテーションを使用してSetFlagsRule
クラスを使用すると、すべてのコードブランチをテストできます。SetFlagsRule.ClassRule
メソッドを使用して、一般的なテストバグを回避します。FlagsParameterization
を使用して、さまざまなフラグ構成でクラスをテストします。
すべてのコードブランチをテストする
静的クラスを使用してフラグにアクセスするプロジェクトの場合、フラグ値をオーバーライドするための SetFlagsRule
ヘルパークラスが用意されています。次のコード スニペットは、SetFlagsRule
を使用して複数のフラグを一度に有効にする方法を示しています。
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import com.example.android.aconfig.demo.flags.Flags;
...
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Test
@EnableFlags({Flags.FLAG_FLAG_FOO, Flags.FLAG_FLAG_BAR})
public void test_flag_foo_and_flag_bar_turned_on() {
...
}
ここで、
@Rule
は、SetFlagsRule
クラスでのフラグと JUnit の依存関係を追加するために使用されるアノテーションです。SetFlagsRule
は、フラグ値をオーバーライドするために用意されたヘルパークラスです。SetFlagsRule
でデフォルト値がどのように決まるかについては、デバイスのデフォルト値をご覧ください。@EnableFlags
は、任意の数のフラグ名を受け入れるアノテーションです。フラグを無効にする場合は、@DisableFlags
を使用します。これらのアノテーションは、メソッドまたはクラスに適用できます。
テストプロセス全体にフラグ値を設定するために、まず SetFlagsRule
を設定します。これは、テスト内の @Before
アノテーションが付いたセットアップ メソッドの前に実行されます。SetFlagsRule
が終了すると、フラグ値は元の状態に戻ります。これは、@After
アノテーションが付いたセットアップ メソッドの後に発生します。
フラグが正しく設定されるようにする
前述のように、SetFlagsRule
は JUnit の @Rule
アノテーションで使用されます。このため SetFlagsRule
では、テストクラスのコンストラクタや、@BeforeClass
または @AfterClass
アノテーション付きのメソッドでフラグが正しく設定されることを保証できません。
テスト フィクスチャが正しいクラス値で作成されるようにするには、SetFlagsRule.ClassRule
メソッドを使用して、@Before
アノテーションが付いたセットアップ メソッドが実行されるまでフィクスチャが作成されないようにします。
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import com.example.android.aconfig.demo.flags.Flags;
class ExampleTest {
@ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
@Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
private DemoClass underTest = new DemoClass();
@Test
@EnableFlags(Flags.FLAG_FLAG_FOO)
public void test_flag_foo_turned_on() {
...
}
}
SetFlagsRule.ClassRule
クラスルールを追加すると、DemoClass
のコンストラクタによって FLAG_FLAG_FOO
が読み取られたときに、test_flag_foo_turned_on
が実行されずに失敗します。
クラス全体でフラグを有効にする必要がある場合は、@EnableFlags
アノテーションをクラスレベル(クラス宣言の前)に移動します。アノテーションをクラスレベルに移動すると、SetFlagsRule.ClassRule
は、テストクラスのコンストラクタや、@BeforeClass
または @AfterClass
アノテーション付きのメソッドでもフラグを正しく設定できるようになります。
複数のフラグ構成でテストを実行する
フラグ値はテストごとに設定できるため、パラメータ化を使用して複数のフラグ構成でテストを実行することもできます。
...
import com.example.android.aconfig.demo.flags.Flags;
...
@RunWith(ParameterizedAndroidJunit4::class)
class FooBarTest {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(Flags.FLAG_FOO, Flags.FLAG_BAR);
}
@Rule
public SetFlagsRule mSetFlagsRule;
public FooBarTest(FlagsParameterization flags) {
mSetFlagsRule = new SetFlagsRule(flags);
}
@Test public void fooLogic() {...}
@DisableFlags(Flags.FLAG_BAR)
@Test public void legacyBarLogic() {...}
@EnableFlags(Flags.FLAG_BAR)
@Test public void newBarLogic() {...}
}
SetFlagsRule
を設定し、パラメータ化をしなかった場合、このクラスは 3 件のテスト(fooLogic
、legacyBarLogic
、newBarLogic
)を実行します。fooLogic
メソッドは、デバイスで設定された FLAG_FOO
および FLAG_BAR
の値で実行されます。
パラメータ化が追加されると、FlagsParameterization.allCombinationsOf
メソッドは、FLAG_FOO
フラグと FLAG_BAR
フラグの可能なすべての組み合わせを作成します。
FLAG_FOO
はtrue
、FLAG_BAR
はtrue
FLAG_FOO
はtrue
、FLAG_BAR
はfalse
FLAG_FOO
はfalse
、FLAG_BAR
はtrue
FLAG_FOO
は false、FLAG_BAR
はfalse
@DisableFlags
アノテーションと @EnableFlags
アノテーションは、フラグ値を直接変更するのではなく、パラメータ条件に基づいてフラグ値を変更します。たとえば、legacyBarLogic
は FLAG_BAR
が無効になっている場合にのみ実行されます。これは、フラグの 4 つの組み合わせのうち 2 つで発生します。他の 2 つの組み合わせでは、legacyBarLogic
はスキップされます。
フラグをパラメータ化するには、次の 2 つのメソッドがあります。
FlagsParameterization.allCombinationsOf(String...)
は、各テストを 2^n 回実行します。たとえば、フラグが 1 つだと 2 回のテストが実行され、フラグが 4 つだと 16 回のテストが実行されます。FlagsParameterization.progressionOf(String...)
は、各テストを n+1 回実行します。たとえば、フラグが 1 つだと 2 回のテストが実行され、フラグが 4 つだと 5 回のテストが実行されます。
単体テストを作成する(C および C++)
AOSP には、GoogleTest フレームワークで記述された C および C++ テスト用のフラグ値マクロが含まれています。
テストソースに、マクロ定義と aconfig で生成されたライブラリを含めます。
#include <flag_macros.h> #include "android_cts_flags.h"
テストソースで、テストケースに
TEST
マクロとTESTF
マクロを使用する代わりに、TEST_WITH_FLAGS
とTEST_F_WITH_FLAGS
を使用します。#define TEST_NS android::cts::flags::tests ... TEST_F_WITH_FLAGS( TestFWithFlagsTest, requies_disabled_flag_enabled_skip, REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(TEST_NS, readwrite_enabled_flag)) ) { TestFail(); } ... TEST_F_WITH_FLAGS( TestFWithFlagsTest, multi_flags_for_same_state_skip, REQUIRES_FLAGS_ENABLED( ACONFIG_FLAG(TEST_NS, readwrite_enabled_flag), LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) ) ) { TestFail(); } ... TEST_WITH_FLAGS( TestWithFlagsTest, requies_disabled_flag_enabled_skip, REQUIRES_FLAGS_DISABLED( LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_enabled_flag)) ) { FAIL(); } ... TEST_WITH_FLAGS( TestWithFlagsTest, requies_enabled_flag_enabled_executed, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_NS, readwrite_enabled_flag)) ) { TestWithFlagsTestHelper::executed_tests.insert( "requies_enabled_flag_enabled_executed"); }
ここで、
TEST
マクロとTEST_F
マクロの代わりにTEST_WITH_FLAGS
マクロとTEST_F_WITH_FLAGS
マクロが使われます。REQUIRES_FLAGS_ENABLED
は、有効化条件を満たす必要がある一連の機能リリースフラグを定義します。このフラグは、ACONFIG_FLAG
マクロまたはLEGACY_FLAG
マクロで記述できます。REQUIRES_FLAGS_DISABLED
は、無効化条件を満たす必要がある一連の機能フラグを定義します。このフラグは、ACONFIG_FLAG
マクロまたはLEGACY_FLAG
マクロで記述できます。ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag)
は、aconfig ファイルで定義されたフラグに使用されるマクロです。このマクロは、名前空間(TEST_NS
)とフラグ名(readwrite_enabled_flag
)を受け入れます。LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag)
は、デバイス設定でデフォルトで設定されるフラグに使用されるマクロです。
Android.bp
ビルドファイルに、aconfig で生成されたライブラリと、関連するマクロ ライブラリをテスト依存関係として追加します。cc_test { name: "FlagMacrosTests", srcs: ["src/FlagMacrosTests.cpp"], static_libs: [ "libgtest", "libflagtest", "my_aconfig_lib", ], shared_libs: [ "libbase", "server_configurable_flags", ], test_suites: ["general-tests"], ... }
次のコマンドを使用して、ローカルでテストを実行します。
atest FlagMacrosTests
フラグ
my_namespace.android.myflag.tests.my_flag
が無効になっている場合、テスト結果は次のようになります。[1/2] MyTest#test1: IGNORED (0ms) [2/2] MyTestF#test2: PASSED (0ms)
フラグ
my_namespace.android.myflag.tests.my_flag
が有効になっている場合、テスト結果は次のようになります。[1/2] MyTest#test1: PASSED (0ms) [2/2] MyTestF#test2: IGNORED (0ms)
フラグ値が変更されないエンドツーエンド テストまたは単体テストを作成する
フラグをオーバーライドできず、現在のフラグ状態に基づいている場合のみテストをフィルタリングできるテストケースの場合は、RequiresFlagsEnabled
アノテーションと RequiresFlagsDisabled
アノテーションでルール CheckFlagsRule
を使用します。
次の手順では、フラグ値をオーバーライドできないエンドツーエンド テストまたは単体テストを作成して実行する方法について説明します。
テストコードで、
CheckFlagsRule
を使用してテスト フィルタリングを適用します。また、Java アノテーションRequiresFlagsEnabled
とRequiredFlagsDisabled
を使用して、テストのフラグ要件を指定します。デバイス側のテストでは、
DeviceFlagsValueProvider
クラスを使用します。@RunWith(JUnit4.class) public final class FlagAnnotationTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Test @RequiresFlagsEnabled(Flags.FLAG_FLAG_NAME_1) public void test1() {} @Test @RequiresFlagsDisabled(Flags.FLAG_FLAG_NAME_1) public void test2() {} }
ホスト側のテストでは、
HostFlagsValueProvider
クラスを使用します。@RunWith(DeviceJUnit4ClassRunner.class) public final class FlagAnnotationTest extends BaseHostJUnit4Test { @Rule public final CheckFlagsRule mCheckFlagsRule = HostFlagsValueProvider.createCheckFlagsRule(this::getDevice); @Test @RequiresFlagsEnabled(Flags.FLAG_FLAG_NAME_1) public void test1() {} @Test @RequiresFlagsDisabled(Flags.FLAG_FLAG_NAME_1) public void test2() {} }
テストのビルドファイルの
static_libs
セクションに、jflag-unit
と aconfig で生成されたライブラリを追加します。android_test { name: "FlagAnnotationTests", srcs: ["*.java"], static_libs: [ "androidx.test.rules", "my_aconfig_lib", "flag-junit", "platform-test-annotations", ], test_suites: ["general-tests"], }
テストをローカルで実行するには、次のコマンドを使用します。
atest FlagAnnotationTests
フラグ
Flags.FLAG_FLAG_NAME_1
が無効になっている場合、テスト結果は次のようになります。[1/2] com.cts.flags.FlagAnnotationTest#test1: ASSUMPTION_FAILED (10ms) [2/2] com.cts.flags.FlagAnnotationTest#test2: PASSED (2ms)
それ以外の場合、テスト結果は次のようになります。
[1/2] com.cts.flags.FlagAnnotationTest#test1: PASSED (2ms) [2/2] com.cts.flags.FlagAnnotationTest#test2: ASSUMPTION_FAILED (10ms)
デバイスのデフォルト値
初期化された SetFlagsRule
は、デバイスのフラグ値を使用します。adb などでデバイスのフラグ値がオーバーライドされていない場合、デフォルト値はビルドのリリース構成と同じです。デバイスの値がオーバーライドされている場合、SetFlagsRule
はオーバーライドされた値をデフォルトとして使用します。
同じテストを異なるリリース構成で実行すると、SetFlagsRule
で明示的に設定されていないフラグの値が異なる場合があります。
各テストの後に、SetFlagsRule
は Flags
の FeatureFlags
インスタンスを元の FeatureFlagsImpl
に復元します。これにより、他のテストメソッドやクラスに副作用が及ぶことがなくなります。