Uji kode dalam tanda peluncuran fitur

Dengan diperkenalkannya tanda peluncuran fitur, ada kebijakan pengujian baru yang harus Anda patuhi:

  • Pengujian Anda harus mencakup perilaku flag yang diaktifkan dan dinonaktifkan.
  • Anda harus menggunakan mekanisme resmi untuk menetapkan nilai flag selama pengujian.
  • Pengujian xTS tidak boleh mengganti nilai flag dalam pengujian.

Bagian berikutnya memberikan mekanisme resmi yang harus Anda gunakan untuk mematuhi kebijakan ini.

Menguji kode yang ditandai

Skenario pengujian Mekanisme yang digunakan
Pengujian lokal saat nilai flag sering berubah Android debug bridge seperti yang dibahas dalam artikel Mengubah nilai flag saat runtime
Pengujian lokal saat nilai flag tidak sering berubah File nilai flag seperti yang dibahas dalam Menetapkan nilai flag peluncuran fitur
Pengujian menyeluruh saat nilai flag berubah FeatureFlagTargetPreparer seperti yang dibahas dalam Membuat pengujian menyeluruh
Pengujian unit saat nilai flag berubah SetFlagsRule dengan @EnableFlags dan @DisableFlags seperti yang dibahas dalam Membuat pengujian unit (Java dan Kotlin) atau Membuat pengujian unit (C dan C++)
Pengujian unit atau menyeluruh dengan nilai flag yang tidak dapat berubah CheckFlagsRule seperti yang dibahas dalam Membuat pengujian unit atau menyeluruh yang nilai flagnya tidak berubah

Membuat pengujian menyeluruh

AOSP menyediakan class yang disebut FeatureFlagTargetPreparer, yang memungkinkan pengujian menyeluruh di perangkat. Class ini menerima penggantian nilai tanda sebagai input, menetapkan tanda tersebut dalam konfigurasi perangkat sebelum eksekusi uji, dan memulihkan tanda setelah dieksekusi.

Anda dapat menerapkan fungsi class FeatureFlagTargetPreparer pada modul pengujian dan tingkat konfigurasi pengujian.

Menerapkan FeatureFlagTargetPreparer dalam konfigurasi modul pengujian

Untuk menerapkan FeatureFlagTargetPreparer dalam konfigurasi modul pengujian, sertakan penggantian nilai FeatureFlagTargetPreparer dan tanda dalam file konfigurasi modul pengujian 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>

Dalam hal ini:

  • target.preparer class selalu ditetapkan ke com.android.tradefed.targetprep.FeatureFlagTargetPreparer.
  • option adalah penggantian flag dengan name yang selalu ditetapkan ke flag-value dan value ditetapkan ke namespace/aconfigPackage.flagName=true|false.

Membuat modul pengujian berparameter berdasarkan status flag

Untuk membuat modul pengujian berparameter berdasarkan status tanda:

  1. Sertakan FeatureFlagTargetPreparer dalam file konfigurasi modul pengujian AndroidTest.xml:

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Tentukan opsi nilai flag di bagian test_module_config pada file build Android.bp:

    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"},
        ],
    }
    

    Kolom options berisi penggantian tanda dengan name yang selalu ditetapkan ke flag-value dan value ditetapkan ke namespace/aconfigPackage.flagName=true|false.

Membuat pengujian unit (Java dan Kotlin)

Bagian ini menjelaskan pendekatan untuk mengganti nilai flag aconfig di tingkat class dan metode (per-test) dalam pengujian Java dan Kotlin.

Untuk menulis pengujian unit otomatis dalam codebase besar dengan banyak flag, ikuti langkah-langkah berikut:

  1. Gunakan class SetFlagsRule dengan anotasi @EnableFlags dan @DisableFlags untuk menguji semua cabang kode.
  2. Gunakan metode SetFlagsRule.ClassRule untuk menghindari bug pengujian umum.
  3. Gunakan FlagsParameterization untuk menguji class Anda di berbagai konfigurasi flag.

Menguji semua cabang kode

Bagi project yang menggunakan class statis untuk mengakses flag, class helper SetFlagsRule disediakan untuk mengganti nilai flag. Cuplikan kode berikut menunjukkan cara menyertakan SetFlagsRule dan mengaktifkan beberapa tanda sekaligus:

  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() {
    ...
    }

Dalam hal ini:

  • @Rule adalah anotasi yang digunakan untuk menambahkan dependensi flag-JUnit dari class SetFlagsRule.
  • SetFlagsRule adalah class bantuan yang disediakan untuk mengganti nilai flag. Untuk mengetahui informasi tentang cara SetFlagsRule menentukan nilai default, lihat Nilai default perangkat.
  • @EnableFlags adalah anotasi yang menerima jumlah arbitrer nama flag. Saat menonaktifkan tanda, gunakan @DisableFlags. Anda dapat menerapkan anotasi ini ke metode atau class.

Tetapkan nilai flag untuk seluruh proses pengujian, dimulai dengan SetFlagsRule, yang sebelum metode penyiapan beranotasi @Before apa pun dalam pengujian tersebut. Nilai flag kembali ke status sebelumnya saat SetFlagsRule selesai, yaitu setelah metode penyiapan beranotasi @After.

Memastikan tanda disetel dengan benar

Seperti yang disebutkan sebelumnya, SetFlagsRule digunakan dengan anotasi @Rule JUnit, yang berarti SetFlagsRule tidak dapat memastikan flag Anda ditetapkan dengan benar selama konstruktor class pengujian, atau metode beranotasi @BeforeClass atau @AfterClass apa pun.

Untuk memastikan bahwa perlengkapan pengujian dibuat dengan nilai class yang benar, gunakan metode SetFlagsRule.ClassRule sehingga perlengkapan Anda tidak dibuat hingga metode penyiapan yang dianotasi @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() {
      ...
    }
  }

Dengan menambahkan aturan class SetFlagsRule.ClassRule, test_flag_foo_turned_on akan gagal sebelum berjalan saat FLAG_FLAG_FOO dibaca oleh konstruktor DemoClass.

Jika seluruh class Anda memerlukan flag yang diaktifkan, pindahkan anotasi @EnableFlags ke tingkat class (sebelum deklarasi class). Memindahkan anotasi ke tingkat class memungkinkan SetFlagsRule.ClassRule memastikan flag ditetapkan dengan benar selama konstruktor class pengujian, atau selama metode yang dianotasi @BeforeClass atau @AfterClass.

Menjalankan pengujian di beberapa konfigurasi flag

Karena Anda dapat menetapkan nilai flag per pengujian, Anda juga dapat menggunakan parameterisasi untuk menjalankan pengujian di beberapa konfigurasi flag:

...
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() {...}
}

Perhatikan bahwa dengan SetFlagsRule, tetapi tanpa parameterisasi, class ini menjalankan tiga pengujian (fooLogic, legacyBarLogic, dan newBarLogic). Metode fooLogic berjalan dengan nilai FLAG_FOO dan FLAG_BAR apa pun yang ditetapkan di perangkat.

Saat parameterisasi ditambahkan, metode FlagsParameterization.allCombinationsOf akan membuat semua kemungkinan kombinasi dari tanda FLAG_FOO dan FLAG_BAR:

  • FLAG_FOO adalah true dan FLAG_BAR adalah true
  • FLAG_FOO adalah true dan FLAG_BAR adalah false
  • FLAG_FOO adalah false dan FLAG_BAR adalah true
  • FLAG_FOO salah dan FLAG_BAR adalah false

Alih-alih mengubah nilai flag secara langsung, anotasi @DisableFlags dan @EnableFlags mengubah nilai flag berdasarkan kondisi parameter. Misalnya, legacyBarLogic hanya berjalan jika FLAG_BAR dinonaktifkan, yang terjadi dalam dua dari empat kombinasi flag. legacyBarLogic dilewati untuk dua kombinasi lainnya.

Ada dua metode untuk membuat parameterisasi untuk flag Anda:

  • FlagsParameterization.allCombinationsOf(String...) mengeksekusi 2^n operasi dari setiap pengujian. Misalnya, satu flag menjalankan 2x pengujian atau empat flag menjalankan 16x pengujian.

  • FlagsParameterization.progressionOf(String...) menjalankan n+1 operasi dari setiap pengujian. Misalnya, satu penanda menjalankan pengujian 2x dan empat penanda menjalankan pengujian 5x.

Membuat pengujian unit (C dan C++)

AOSP menyertakan makro nilai tanda untuk pengujian C dan C++ yang ditulis dalam framework GoogleTest.

  1. Dalam sumber pengujian, sertakan definisi makro dan library yang dihasilkan aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. Di sumber pengujian, gunakan TEST_WITH_FLAGS dan TEST_F_WITH_FLAGS, bukan makro TEST dan TESTF untuk kasus pengujian:

    #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");
    }
    

    Dalam hal ini:

    • Makro TEST_WITH_FLAGS dan TEST_F_WITH_FLAGS digunakan, bukan makro TEST dan TEST_F.
    • REQUIRES_FLAGS_ENABLED menentukan kumpulan tanda rilis fitur yang harus memenuhi kondisi diaktifkan. Anda dapat menulis flag ini dalam makro ACONFIG_FLAG atau LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED menentukan kumpulan tombol fitur yang harus memenuhi kondisi dinonaktifkan. Anda dapat menulis flag ini dalam makro ACONFIG_FLAG atau LEGACY_FLAG.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) adalah makro yang digunakan untuk flag yang ditentukan dalam file konfigurasi. Makro ini menerima namespace (TEST_NS) dan nama flag (readwrite_enabled_flag).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) adalah makro yang digunakan untuk flag yang ditetapkan dalam konfigurasi perangkat secara default.
  3. Dalam file build Android.bp, tambahkan library yang dihasilkan aconfig dan library makro yang relevan sebagai dependensi pengujian:

    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. Jalankan pengujian secara lokal dengan perintah ini:

    atest FlagMacrosTests
    

    Jika tanda my_namespace.android.myflag.tests.my_flag dinonaktifkan, hasil pengujiannya adalah:

    [1/2] MyTest#test1: IGNORED (0ms)
    [2/2] MyTestF#test2: PASSED (0ms)
    

    Jika tanda my_namespace.android.myflag.tests.my_flag diaktifkan, hasil pengujiannya adalah:

    [1/2] MyTest#test1: PASSED (0ms)
    [2/2] MyTestF#test2: IGNORED (0ms)
    

Membuat pengujian unit atau menyeluruh yang nilai flagnya tidak berubah

Untuk kasus pengujian saat Anda tidak dapat mengganti flag dan hanya dapat memfilter pengujian jika berdasarkan status flag saat ini, gunakan aturan CheckFlagsRule dengan anotasi RequiresFlagsEnabled dan RequiresFlagsDisabled.

Langkah-langkah berikut menunjukkan cara membuat dan menjalankan pengujian menyeluruh atau unit yang tidak dapat mengganti nilai flag:

  1. Dalam kode pengujian, gunakan CheckFlagsRule untuk menerapkan pemfilteran pengujian. Selain itu, gunakan anotasi Java RequiresFlagsEnabled dan RequiredFlagsDisabled untuk menentukan persyaratan flag dalam pengujian Anda.

    Pengujian sisi perangkat menggunakan class 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() {}
    }
    

    Pengujian sisi host menggunakan class 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. Tambahkan jflag-unit dan library yang dihasilkan aconfig ke bagian static_libs file build untuk pengujian Anda:

    android_test {
        name: "FlagAnnotationTests",
        srcs: ["*.java"],
        static_libs: [
            "androidx.test.rules",
            "my_aconfig_lib",
            "flag-junit",
            "platform-test-annotations",
        ],
        test_suites: ["general-tests"],
    }
    
  3. Gunakan perintah berikut untuk menjalankan pengujian secara lokal:

    atest FlagAnnotationTests
    

    Jika flag Flags.FLAG_FLAG_NAME_1 dinonaktifkan, hasil pengujiannya adalah:

    [1/2] com.cts.flags.FlagAnnotationTest#test1: ASSUMPTION_FAILED (10ms)
    [2/2] com.cts.flags.FlagAnnotationTest#test2: PASSED (2ms)
    

    Jika tidak, hasil pengujiannya adalah:

    [1/2] com.cts.flags.FlagAnnotationTest#test1: PASSED (2ms)
    [2/2] com.cts.flags.FlagAnnotationTest#test2: ASSUMPTION_FAILED (10ms)
    

Nilai default perangkat

SetFlagsRule yang diinisialisasi menggunakan nilai tanda dari perangkat. Jika nilai tanda di perangkat tidak diganti, seperti dengan adb, nilai default akan sama dengan konfigurasi rilis build. Jika nilai di perangkat telah digantikan, SetFlagsRule akan menggunakan nilai penggantian sebagai default.

Jika pengujian yang sama dijalankan dalam konfigurasi rilis yang berbeda, nilai flag yang tidak ditetapkan secara eksplisit dengan SetFlagsRule dapat bervariasi.

Setelah setiap pengujian, SetFlagsRule akan memulihkan instance FeatureFlags di Flags ke FeatureFlagsImpl aslinya, sehingga tidak memiliki efek samping pada metode dan class pengujian lainnya.