測試功能發布旗標中的程式碼

隨著功能推出標記的推出,您必須遵守新的測試政策:

  • 您的測試必須涵蓋標記的啟用和停用行為。
  • 您必須使用官方機制,才能在測試期間設定標記值。
  • 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

根據標記狀態建立參數化測試模組

如要根據標記狀態建立參數化測試模組,請按照下列步驟操作:

  1. AndroidTest.xml 測試模組設定檔中加入 FeatureFlagTargetPreparer

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. 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-valuevalue 設為 namespace/aconfigPackage.flagName=true|false

建立單元測試 (Java 和 Kotlin)

本節說明在 Java 和 Kotlin 測試中,如何在類別和方法層級 (個別測試) 覆寫 aconfig 標記值。

如要在含有大量標記的大型程式碼庫中編寫自動單元測試,請按照下列步驟操作:

  1. 使用 SetFlagsRule 類別搭配 @EnableFlags@DisableFlags 註解,測試所有程式碼分支。
  2. 使用 SetFlagsRule.ClassRule 方法避免常見的測試錯誤。
  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. 在測試來源中,請使用 TEST_WITH_FLAGSTEST_F_WITH_FLAGS,而非在測試案例中使用 TESTTESTF 巨集:

    #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 建構檔案中,將 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 產生的程式庫新增至測試的建構檔案 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),則預設值會與版本的發布設定相同。如果裝置上的值已遭到覆寫,SetFlagsRule 會使用覆寫值做為預設值。

如果在不同的版本設定下執行相同的測試,未明確設定 SetFlagsRule 的標記值可能會有所不同。

在每次測試後,SetFlagsRule 會將 Flags 中的 FeatureFlags 例項還原為原始 FeatureFlagsImpl,以免對其他測試方法和類別產生副作用。