Testa il codice all'interno dei flag di lancio delle funzionalità

Con l'introduzione dei flag di lancio delle funzionalità, sono previste nuove norme di test a cui devi attenerti:

  • I test devono coprire i comportamenti sia abilitati che disattivati del flag.
  • Devi utilizzare i meccanismi ufficiali per impostare i valori dei flag durante i test.
  • I test xTS non devono sostituire i valori dei flag nei test.

La sezione successiva illustra i meccanismi ufficiali che devi utilizzare per rispettare queste norme.

Testare il codice segnalato

Scenario di test Meccanismo utilizzato
Test locali quando i valori degli indicatori cambiano spesso Android Debug Bridge, come descritto in Modificare il valore di un flag in fase di runtime
Test locale quando i valori degli indicatori non cambiano spesso File dei valori dei flag, come descritto in Impostare i valori dei flag di lancio della funzionalità
Test end-to-end in cui i valori dei flag cambiano FeatureFlagTargetPreparer come descritto in Creare test end-to-end
Test di unità in cui i valori di flag cambiano SetFlagsRule con @EnableFlags e @DisableFlags come descritto in Creare test di unità (Java e Kotlin) o Creare test di unità (C e C++)
Test di unità o end-to-end in cui i valori degli indicatori non possono cambiare CheckFlagsRule come descritto in Creare test end-to-end o unitari in cui i valori dei flag non cambiano

Creare test end-to-end

AOSP fornisce una classe chiamata FeatureFlagTargetPreparer, che consente di eseguire test end-to-end su un dispositivo. Questa classe accetta come input le sostituzioni dei valori dei flag, li imposta nella configurazione dei dispositivi prima dell'esecuzione del test e li ripristina dopo l'esecuzione.

Puoi applicare la funzionalità della classe FeatureFlagTargetPreparer ai livelli di modulo di test e configurazione di test.

Applicare FeatureFlagTargetPreparer in una configurazione del modulo di test

Per applicare FeatureFlagTargetPreparer in una configurazione del modulo di test, includi le sostituzioni di FeatureFlagTargetPreparer e del valore del flag nel file di configurazione del modulo di test 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>

Dove:

  • target.preparer class è sempre impostato su com.android.tradefed.targetprep.FeatureFlagTargetPreparer.
  • option è l'override del flag con name sempre impostato su flag-value e value impostato su namespace/aconfigPackage.flagName=true|false.

Creare moduli di test parametrizzati in base agli stati dei flag

Per creare moduli di test parametrizzati in base agli stati dei flag:

  1. Includi FeatureFlagTargetPreparer nel file di configurazione del modulo di test AndroidTest.xml:

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Specifica le opzioni di valore del flag nella sezione test_module_config di un file di compilazione 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"},
        ],
    }
    

    Il campo options contiene le sostituzioni dei flag con name impostato sempre su flag-value e value impostato su namespace/aconfigPackage.flagName=true|false.

Creare test delle unità (Java e Kotlin)

Questa sezione descrive l'approccio per eseguire l'override dei valori del flag aconfig a livello di classe e metodo (per test) nei test Java e Kotlin.

Per scrivere test di unità automatici in una base di codice di grandi dimensioni con un numero elevato di flag:

  1. Utilizza la classe SetFlagsRule con le annotazioni @EnableFlags e @DisableFlags per testare tutti i rami di codice.
  2. Utilizza il metodo SetFlagsRule.ClassRule per evitare i bug di test più comuni.
  3. Utilizza FlagsParameterization per testare le tue classi in un ampio insieme di configurazioni di flag.

Testare tutti i rami di codice

Per i progetti che utilizzano la classe statica per accedere ai flag, viene fornita la classe di assistenza SetFlagsRule per eseguire l'override dei valori dei flag. Il seguente snippet di codice mostra come includere SetFlagsRule e attivare più flag contemporaneamente:

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

Dove:

  • @Rule è un'annotazione utilizzata per aggiungere la dipendenza flag-JUnit della classe SetFlagsRule.
  • SetFlagsRule è la classe di assistenza fornita per sostituire i valori dei flag. Per informazioni su come SetFlagsRule determina i valori predefiniti, consulta Valori predefiniti del dispositivo.
  • @EnableFlags è un'annotazione che accetta un numero arbitrario di nomi di indicatori. Quando disattivi i flag, utilizza @DisableFlags. Puoi applicare queste annotazione a un metodo o a una classe.

Imposta i valori dei flag per l'intero processo di test, a partire da SetFlagsRule, che precede qualsiasi metodo di configurazione annotato con @Before nel test. I valori di flag tornano allo stato precedente al termine di SetFlagsRule, ovvero dopo eventuali metodi di configurazione annotati con @After.

Assicurati che gli indicatori siano impostati correttamente

Come accennato in precedenza, SetFlagsRule viene utilizzato con l'annotazione @Rule JUnit, il che significa che SetFlagsRule non può garantire che i flag siano impostati correttamente durante il costruttore della classe di test o in qualsiasi metodo annotato con @BeforeClass o @AfterClass.

Per assicurarti che i fixture di test vengano costruiti con il valore di classe corretto, utilizza il metodo SetFlagsRule.ClassRule in modo che i fixture non vengano creati finché non viene utilizzato un metodo di configurazione annotato con @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() {
      ...
    }
  }

Aggiungendo la regola della classe SetFlagsRule.ClassRule, test_flag_foo_turned_on non viene eseguito prima dell'esecuzione quando FLAG_FLAG_FOO viene letto dal costruttore di DemoClass.

Se è necessario attivare un flag per l'intero corso, sposta l'annotazione @EnableFlags al livello di corso (prima della dichiarazione del corso). Spostare l'annotazione a livello di classe consente a SetFlagsRule.ClassRule di assicurarsi che il flag sia impostato correttamente durante il costruttore della classe di test o durante i metodi con annotazione @BeforeClass o @AfterClass.

Eseguire test su più configurazioni di flag

Poiché puoi impostare i valori dei flag in base al test, puoi anche utilizzare la parametrizzazione per eseguire test su più configurazioni di 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() {...}
}

Tieni presente che con SetFlagsRule, ma senza parametrizzazione, questa classe esegue tre test (fooLogic, legacyBarLogic e newBarLogic). Il metodo fooLogic viene eseguito con i valori impostati per FLAG_FOO e FLAG_BAR sul dispositivo.

Quando viene aggiunta la parametrizzazione, il metodo FlagsParameterization.allCombinationsOf crea tutte le combinazioni possibili degli indicatori FLAG_FOO e FLAG_BAR:

  • FLAG_FOO è true e FLAG_BAR è true
  • FLAG_FOO è true e FLAG_BAR è false
  • FLAG_FOO è false e FLAG_BAR è true
  • FLAG_FOO è false e FLAG_BAR è false

Anziché modificare direttamente i valori dei flag, le annotazioni @DisableFlags e @EnableFlags modificano i valori dei flag in base alle condizioni dei parametri. Ad esempio, legacyBarLogic viene eseguito solo quando FLAG_BAR è disattivato, il che si verifica in due delle quattro combinazioni di flag. legacyBarLogic viene ignorato per le altre due combinazioni.

Esistono due metodi per creare le parametrizzazioni per i flag:

  • FlagsParameterization.allCombinationsOf(String...) esegue 2^n esecuzioni di ogni test. Ad esempio, un flag esegue test 2x o quattro flag eseguono test 16x.

  • FlagsParameterization.progressionOf(String...) esegue n+1 esecuzioni di ogni test. Ad esempio, un flag esegue 2 volte i test e quattro flag eseguono 5 volte i flag.

Creare test di unità (C e C++)

AOSP include macro per i valori di flag per i test C e C++ scritti nel framework GoogleTest.

  1. Nella sorgente del test, includi le definizioni delle macro e le librerie generate da aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. Nell'origine del test, anziché utilizzare le macro TEST e TESTF per i casi di test, utilizza TEST_WITH_FLAGS e 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");
    }
    

    Dove:

    • Le macro TEST_WITH_FLAGS e TEST_F_WITH_FLAGS vengono utilizzate al posto delle macro TEST e TEST_F.
    • REQUIRES_FLAGS_ENABLED definisce un insieme di flag di rilascio delle funzionalità che devono soddisfare la condizione di abilitazione. Puoi scrivere questi flag nelle macro ACONFIG_FLAG o LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED definisce un insieme di flag di funzionalità che devono soddisfare la condizione di disattivazione. Puoi scrivere questi flag nelle macro ACONFIG_FLAG o LEGACY_FLAG.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) è una macro utilizzata per i flag definiti nei file di configurazione. Questa macro accetta un ambito (TEST_NS) e un nome di opzione (readwrite_enabled_flag).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) è una macro utilizzata per i flag impostati nella configurazione del dispositivo per impostazione predefinita.
  3. Nel file di compilazione Android.bp, aggiungi le librerie generate da aconfig e le librerie di macro pertinenti come dipendenza di test:

    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. Esegui i test in locale con questo comando:

    atest FlagMacrosTests
    

    Se il flag my_namespace.android.myflag.tests.my_flag è disattivato, il risultato del test è:

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

    Se il flag my_namespace.android.myflag.tests.my_flag è attivato, il risultato del test è:

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

Crea test end-to-end o unitari in cui i valori di flag non cambiano

Per i casi di test in cui non puoi ignorare gli indicatori e puoi filtrare i test solo se si basano sullo stato corrente dell'indicatore, utilizza la regola CheckFlagsRule con le annotazioni RequiresFlagsEnabled e RequiresFlagsDisabled.

I passaggi riportati di seguito mostrano come creare ed eseguire un test end-to-end o di unità in cui i valori di flag non possono essere sostituiti:

  1. Nel codice di test, utilizza CheckFlagsRule per applicare il filtro dei test. Inoltre, utilizza le annotazioni Java RequiresFlagsEnabled e RequiredFlagsDisabled per specificare i requisiti dei flag per il test.

    Il test lato dispositivo utilizza la classe 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() {}
    }
    

    Il test lato host utilizza la classe 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. Aggiungi jflag-unit e le librerie generate da aconfig alla sezione static_libs del file di compilazione per il test:

    android_test {
        name: "FlagAnnotationTests",
        srcs: ["*.java"],
        static_libs: [
            "androidx.test.rules",
            "my_aconfig_lib",
            "flag-junit",
            "platform-test-annotations",
        ],
        test_suites: ["general-tests"],
    }
    
  3. Utilizza il seguente comando per eseguire il test localmente:

    atest FlagAnnotationTests
    

    Se il flag Flags.FLAG_FLAG_NAME_1 è disattivato, il risultato del test è:

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

    In caso contrario, il risultato del test è:

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

Valori predefiniti del dispositivo

SetFlagsRule inizializzato utilizza i valori di flag del dispositivo. Se il valore del flag sul dispositivo non viene sostituito, ad esempio con adb, il valore predefinito corrisponde a quello della configurazione di release della build. Se il valore sul dispositivo è stato override, SetFlagsRule utilizza il valore di override come valore predefinito.

Se lo stesso test viene eseguito in configurazioni di release diverse, il valore dei flag non impostati esplicitamente con SetFlagsRule può variare.

Dopo ogni test, SetFlagsRule ripristina l'istanza FeatureFlags in Flags al suo FeatureFlagsImpl originale, in modo che non abbia effetti collaterali su altri metodi e classi di test.