功能发布标志中的测试代码

随着功能发布标志的引入,您必须遵循一些新的测试政策:

  • 您的测试必须涵盖标志的已启用和已停用行为。
  • 在测试期间,您必须使用官方机制来设置标志值。
  • 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-valuevalue 设置为 namespace/aconfigPackage.flagName=true|false

根据标志状态创建参数化测试模块

如需根据标志状态创建参数化测试模块,请执行以下操作:

  1. AndroidTest.xml 测试模块配置文件中添加 FeatureFlagTargetPreparer

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. 您可以在 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-valuevalue 设置为 namespace/aconfigPackage.flagName=true|false

创建单元测试(Java 和 Kotlin)

本部分介绍了在 Java 和 Kotlin 测试中覆盖类和方法级(按测试)的 aconfig 标志值的方法。

如需在包含大量标志的大型代码库中编写自动化单元测试,请按以下步骤操作:

  1. 使用带有 @EnableFlags@DisableFlags 注解的 SetFlagsRule 类测试所有代码分支。
  2. 使用 SetFlagsRule.ClassRule 方法可避免常见的测试 bug。
  3. 使用 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,但未进行参数化,此类会运行三个测试(fooLogiclegacyBarLogicnewBarLogic)。fooLogic 方法会以在设备上设置 FLAG_FOOFLAG_BAR 的任何值运行。

添加参数化后,FlagsParameterization.allCombinationsOf 方法会创建 FLAG_FOOFLAG_BAR 标志的所有可能组合:

  • FLAG_FOOtrueFLAG_BARtrue
  • FLAG_FOOtrueFLAG_BARfalse
  • FLAG_FOOfalseFLAG_BARtrue
  • FLAG_FOO 为 false,FLAG_BARfalse

@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++ 测试的标志值宏。

中找到本部分中的代码
  1. 在测试源代码中,添加宏定义和 aconfig 生成的库:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. 在测试源代码中,不要对测试用例使用 TESTTESTF 宏,而是使用 TEST_WITH_FLAGSTEST_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_FLAGSTEST_F_WITH_FLAGS 宏,而不是 TESTTEST_F 宏。
    • REQUIRES_FLAGS_ENABLED 定义了一组必须满足已启用条件的功能版本标志。您可以在 ACONFIG_FLAGLEGACY_FLAG 宏中编写这些标志。
    • REQUIRES_FLAGS_DISABLED 定义了一组必须满足已停用条件的功能标志。您可以在 ACONFIG_FLAGLEGACY_FLAG 宏中编写这些标志。
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) 是用于在 aconfig 文件中定义的标志的宏。此宏接受一个命名空间 (TEST_NS) 和一个标志名称 (readwrite_enabled_flag)。
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) 是一个宏,用于默认在设备配置中设置的标志。
  3. 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"],
      ...
    }
    
  4. 使用以下命令在本地运行测试:

    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 规则与 RequiresFlagsEnabledRequiresFlagsDisabled 注解结合使用。

以下步骤展示了如何在无法替换标志值的情况下创建和运行端到端测试或单元测试:

  1. 在测试代码中,使用 CheckFlagsRule 应用测试过滤。此外,请使用 Java 注解 RequiresFlagsEnabledRequiredFlagsDisabled 指定测试的标志要求。

    设备端测试使用 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() {}
    }
    
  2. 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"],
    }
    
  3. 使用以下命令在本地运行测试:

    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,以免对其他测试方法和类产生副作用。