随着功能发布标志的引入,您必须遵循一些新的测试政策:
- 您的测试必须涵盖标志的已启用和已停用行为。
- 在测试期间,您必须使用官方机制来设置标志值。
- xTS 测试不应替换测试中的标志值。
下一部分将介绍您为遵守这些政策而必须使用的官方机制。
测试被标记的代码
测试场景 | 使用的机制 |
---|---|
标志值经常更改时进行本地测试 | Android 调试桥(如在运行时更改标志的值中所述) |
当标志值不经常更改时进行本地测试 | 标志值文件(如设置功能发布标志值中所述) |
标志值发生变化的端到端测试 | FeatureFlagTargetPreparer ,如创建端到端测试中所述 |
标志值发生变化的单元测试 | 将 SetFlagsRule 替换为 @EnableFlags 和 @DisableFlags (如创建单元测试(Java 和 Kotlin)或创建单元测试(C 和 C++)中所述) |
标志值无法更改的端到端测试或单元测试 | 创建标志值不变的端到端测试或单元测试中所述的 CheckFlagsRule |
创建端到端测试
AOSP 提供了一个名为 FeatureFlagTargetPreparer
的类,可用于在设备上进行端到端测试。此类接受标志值替换项作为输入,在测试执行之前在设备配置中设置这些标志,并在执行后恢复标志。
您可以在测试模块和测试配置级别应用 FeatureFlagTargetPreparer
类的功能。
在测试模块配置中应用 FeatureFlagTargetPreparer
如需在测试模块配置中应用 FeatureFlagTargetPreparer
,请在 AndroidTest.xml
测试模块配置文件中添加 FeatureFlagTargetPreparer
和标志值替换项:
<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
。
根据标志状态创建参数化测试模块
如需根据标志状态创建参数化测试模块,请执行以下操作:
在
AndroidTest.xml
测试模块配置文件中添加FeatureFlagTargetPreparer
:<target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
您可以在
Android.bp
build 文件的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
方法可避免常见的测试 bug。 - 使用
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
,但未进行参数化,此类会运行三个测试(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
停用时运行,停用(出现在四个标志组合中的两个中)。系统会跳过其他两个组合中的 legacyBarLogic
。
您可以通过以下两种方法为标志创建参数化:
FlagsParameterization.allCombinationsOf(String...)
会为每个测试执行 2^n 次运行。例如,一个标志运行 2 倍测试,四个标志运行 16 倍测试。FlagsParameterization.progressionOf(String...)
对每个测试执行 n+1 次运行。例如,一个标志会运行 2 倍的测试,四个标志会运行 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_WITH_FLAGS
和TEST_F_WITH_FLAGS
宏,而不是TEST
和TEST_F
宏。 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
build 文件中,将 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)
创建标记值不会更改的端到端测试或单元测试
对于无法替换标志且只能根据当前标志状态过滤测试的测试用例,请将 CheckFlagsRule
规则与 RequiresFlagsEnabled
和 RequiresFlagsDisabled
注解结合使用。
以下步骤展示了如何在无法替换标志值的情况下创建和运行端到端测试或单元测试:
在测试代码中,使用
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() {} }
将
jflag-unit
和 aconfig 生成的库添加到测试 build 文件的static_libs
部分: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),则默认值与 build 的发布配置相同。如果设备上的值已被替换,则 SetFlagsRule
会将替换值用作默认值。
如果在不同的发布配置下执行同一测试,未使用 SetFlagsRule
明确设置的标志的值可能会有所不同。
在每次测试后,SetFlagsRule
都会将 Flags
中的 FeatureFlags
实例恢复为其原始 FeatureFlagsImpl
,以免对其他测试方法和类产生副作用。