Menguji kode dalam flag peluncuran fitur

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

  • Pengujian Anda harus mencakup perilaku aktif dan nonaktif flag.
  • Anda harus menggunakan mekanisme resmi untuk menetapkan nilai flag selama pengujian.
  • Pengujian xTS tidak boleh menggantikan 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 tanda sering berubah Android Debug Bridge seperti yang dibahas dalam Mengubah nilai flag saat runtime
Pengujian lokal saat nilai flag tidak sering berubah File nilai tanda seperti yang dibahas dalam Menetapkan nilai tanda 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 end-to-end yang nilai tanda fiturnya tidak dapat berubah CheckFlagsRule seperti yang dibahas dalam Membuat pengujian unit atau end-to-end saat nilai tanda tidak berubah

Membuat pengujian menyeluruh

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

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

Menerapkan FeatureFlagTargetPreparer dalam konfigurasi modul pengujian

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

  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 dari 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 flag 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 pengujian) dalam pengujian Java dan Kotlin.

Untuk menulis pengujian unit otomatis dalam codebase besar dengan sejumlah besar tanda, 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

Untuk 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 secara bersamaan:

  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 helper yang disediakan untuk mengganti nilai tanda. Untuk informasi tentang cara SetFlagsRule menentukan nilai default, lihat Nilai default perangkat.
  • @EnableFlags adalah anotasi yang menerima sejumlah nama flag arbitrer. Saat menonaktifkan flag, gunakan @DisableFlags. Anda dapat menerapkan anotasi ini ke metode atau class.

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

Pastikan tanda ditetapkan dengan benar

Seperti yang disebutkan sebelumnya, SetFlagsRule digunakan dengan anotasi JUnit @Rule, yang berarti bahwa SetFlagsRule tidak dapat memastikan tanda Anda disetel dengan benar selama konstruktor class pengujian, atau metode yang dianotasi @BeforeClass atau @AfterClass.

Untuk memastikan bahwa fixture pengujian dibuat dengan nilai class yang benar, gunakan metode SetFlagsRule.ClassRule sehingga fixture Anda tidak dibuat hingga metode penyiapan yang diberi anotasi @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 gagal sebelum dijalankan saat FLAG_FLAG_FOO dibaca oleh konstruktor DemoClass.

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

Menjalankan pengujian di beberapa konfigurasi tanda

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

...
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 yang ditetapkan di perangkat.

Saat parameterisasi ditambahkan, metode FlagsParameterization.allCombinationsOf membuat semua kemungkinan kombinasi flag 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 saat FLAG_BAR dinonaktifkan, yang terjadi dalam dua dari empat kombinasi tanda. legacyBarLogic dilewati untuk dua kombinasi lainnya.

Ada dua metode untuk membuat parameterisasi untuk tanda Anda:

  • FlagsParameterization.allCombinationsOf(String...) menjalankan 2^n kali setiap pengujian. Misalnya, satu tanda menjalankan pengujian 2x atau empat tanda menjalankan pengujian 16x.

  • FlagsParameterization.progressionOf(String...) mengeksekusi n+1 eksekusi setiap pengujian. Misalnya, satu tanda menjalankan pengujian 2x dan empat tanda 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 dibuat aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. Di sumber pengujian, alih-alih menggunakan makro TEST dan TESTF untuk kasus pengujian, gunakan TEST_WITH_FLAGS dan 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");
    }
    

    Dalam hal ini:

    • Makro TEST_WITH_FLAGS dan TEST_F_WITH_FLAGS digunakan, bukan makro TEST dan TEST_F.
    • REQUIRES_FLAGS_ENABLED menentukan serangkaian tanda rilis fitur yang harus memenuhi kondisi yang diaktifkan. Anda dapat menulis flag ini dalam makro ACONFIG_FLAG atau LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED menentukan serangkaian tanda 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 tanda yang ditentukan dalam file konfigurasi. Makro ini menerima namespace (TEST_NS) dan nama tanda (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 flag-nya tidak berubah

Untuk kasus pengujian yang tidak dapat mengganti tanda dan hanya dapat memfilter pengujian jika didasarkan pada status tanda saat ini, gunakan aturan CheckFlagsRule dengan anotasi RequiresFlagsEnabled dan RequiresFlagsDisabled.

Langkah-langkah berikut menunjukkan cara membuat dan menjalankan pengujian unit atau end-to-end yang nilai flag-nya tidak dapat diganti:

  1. Di kode pengujian, gunakan CheckFlagsRule untuk menerapkan pemfilteran pengujian. Selain itu, gunakan anotasi Java RequiresFlagsEnabled dan RequiredFlagsDisabled untuk menentukan persyaratan tanda untuk 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 dibuat 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 tanda 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 flag di perangkat tidak diganti, seperti dengan adb, maka nilai defaultnya sama dengan konfigurasi rilis build. Jika nilai di perangkat telah diganti, SetFlagsRule akan menggunakan nilai pengganti sebagai default.

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

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