Tester le code dans les flags de lancement de fonctionnalités

Avec l'introduction des indicateurs de lancement de fonctionnalités, vous devez respecter de nouvelles règles de test :

  • Vos tests doivent couvrir les comportements activés et désactivés de l'indicateur.
  • Vous devez utiliser les mécanismes officiels pour définir les valeurs des indicateurs lors des tests.
  • Les tests xTS ne doivent pas remplacer les valeurs des indicateurs dans les tests.

La section suivante fournit les mécanismes officiels que vous devez utiliser pour respecter ces règles.

Tester le code avec indicateur

Scénario de test Mécanisme utilisé
Test local lorsque les valeurs des indicateurs changent souvent Android Debug Bridge, comme indiqué dans Modifier la valeur d'un indicateur au moment de l'exécution
Test local lorsque les valeurs des indicateurs ne changent pas souvent Fichier de valeurs d'indicateurs, comme indiqué dans Définir les valeurs des indicateurs de lancement de fonctionnalités
Test de bout en bout lorsque les valeurs des indicateurs changent FeatureFlagTargetPreparer comme indiqué dans Créer des tests de bout en bout
Test unitaire lorsque les valeurs des indicateurs changent SetFlagsRule avec @EnableFlags et @DisableFlags, comme indiqué dans Créer des tests unitaires (Java et Kotlin) ou Créer des tests unitaires (C et C++)
Test de bout en bout ou test unitaire lorsque les valeurs des indicateurs ne peuvent pas changer CheckFlagsRule, comme indiqué dans Créer des tests de bout en bout ou des tests unitaires lorsque les valeurs des indicateurs ne changent pas

Créer des tests de bout en bout

AOSP fournit une classe appelée FeatureFlagTargetPreparer, qui permet d'effectuer des tests de bout en bout sur un appareil. Cette classe accepte les remplacements de valeurs d'indicateurs en entrée, définit ces indicateurs dans la configuration des appareils avant l'exécution du test et les restaure après l'exécution.

Vous pouvez appliquer la fonctionnalité de la classe FeatureFlagTargetPreparer aux niveaux du module de test et de la configuration de test.

Appliquer FeatureFlagTargetPreparer dans une configuration de module de test

Pour appliquer FeatureFlagTargetPreparer dans une configuration de module de test, incluez FeatureFlagTargetPreparer et les remplacements de valeurs d'indicateurs dans le fichier de configuration du module de 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>

Où :

  • target.preparer class est toujours défini sur com.android.tradefed.targetprep.FeatureFlagTargetPreparer.
  • option est le remplacement d'indicateur avec name toujours défini sur flag-value et value défini sur namespace/aconfigPackage.flagName=true|false.

Créer des modules de test paramétrés en fonction des états des indicateurs

Pour créer des modules de test paramétrés en fonction des états des indicateurs :

  1. Incluez FeatureFlagTargetPreparer dans le fichier de configuration du module de test AndroidTest.xml :

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Spécifiez les options de valeurs d'indicateurs dans la section test_module_config d'un fichier de compilation 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"},
        ],
    }
    

    Le champ options contient les remplacements d'indicateurs avec name toujours défini sur flag-value et value défini sur namespace/aconfigPackage.flagName=true|false.

Créer des tests unitaires (Java et Kotlin)

Cette section décrit l'approche à suivre pour remplacer les valeurs des indicateurs aconfig au niveau de la classe et de la méthode (par test) dans les tests Java et Kotlin.

Pour écrire des tests unitaires automatisés dans une base de code volumineuse avec un grand nombre d'indicateurs, procédez comme suit :

  1. Utilisez la classe SetFlagsRule avec les annotations @EnableFlags et @DisableFlags pour tester toutes les branches de code.
  2. Utilisez la méthode SetFlagsRule.ClassRule pour éviter les bugs de test courants.
  3. Utilisez FlagsParameterization pour tester vos classes sur un large éventail de configurations d'indicateurs.

Tester toutes les branches de code

Pour les projets qui utilisent la classe statique pour accéder aux indicateurs, la classe d'assistance SetFlagsRule est fournie pour remplacer les valeurs des indicateurs. L'extrait de code suivant montre comment inclure SetFlagsRule et activer plusieurs indicateurs à la fois :

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

Où :

  • @Rule est une annotation utilisée pour ajouter la dépendance flag-JUnit de la classe SetFlagsRule.
  • SetFlagsRule est une classe d'assistance fournie pour remplacer les valeurs des indicateurs. Pour savoir comment SetFlagsRule détermine les valeurs par défaut, consultez Valeurs par défaut de l'appareil.
  • @EnableFlags est une annotation qui accepte un nombre arbitraire de noms d'indicateurs. Lorsque vous désactivez des indicateurs, utilisez @DisableFlags. Vous pouvez appliquer ces annotations à une méthode ou à une classe.

Définissez les valeurs des indicateurs pour l'ensemble du processus de test, en commençant par SetFlagsRule, qui précède toutes les méthodes de configuration annotées @Before dans le test. Les valeurs des indicateurs reviennent à leur état précédent lorsque SetFlagsRule se termine, c'est-à-dire après toutes les méthodes de configuration annotées @After.

S'assurer que les indicateurs sont correctement définis

Comme mentionné précédemment, SetFlagsRule est utilisé avec l'annotation JUnit @Rule, ce qui signifie que SetFlagsRule ne peut pas garantir que vos indicateurs sont correctement définis lors du constructeur de la classe de test, ni dans les méthodes annotées @BeforeClass ou @AfterClass.

Pour vous assurer que les outils de test sont construits avec la valeur de classe correcte, utilisez la méthode SetFlagsRule.ClassRule afin que vos outils ne soient pas créés tant qu'une méthode de configuration annotée @Before n'est pas utilisée :

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

En ajoutant la règle de classe SetFlagsRule.ClassRule, test_flag_foo_turned_on échoue avant l'exécution lorsque FLAG_FLAG_FOO est lu par le constructeur de DemoClass.

Si l'ensemble de votre classe nécessite qu'un indicateur soit activé, déplacez l'annotation @EnableFlags au niveau de la classe (avant la déclaration de la classe). Le déplacement de l'annotation au niveau de la classe permet à SetFlagsRule.ClassRule de s'assurer que l'indicateur est correctement défini lors du constructeur de la classe de test, ou lors de toute méthode annotée @BeforeClass ou @AfterClass.

Exécuter des tests sur plusieurs configurations d'indicateurs

Étant donné que vous pouvez définir des valeurs d'indicateurs par test, vous pouvez également utiliser la paramétrisation pour exécuter des tests sur plusieurs configurations d'indicateurs :

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

Notez qu'avec SetFlagsRule, mais sans paramétrisation, cette classe exécute trois tests (fooLogic, legacyBarLogic et newBarLogic). La méthode fooLogic s'exécute avec les valeurs de FLAG_FOO et FLAG_BAR définies sur l'appareil.

Lorsque la paramétrisation est ajoutée, la méthode FlagsParameterization.allCombinationsOf crée toutes les combinaisons possibles des indicateurs FLAG_FOO et FLAG_BAR :

  • FLAG_FOO est true et FLAG_BAR est true
  • FLAG_FOO est true et FLAG_BAR est false
  • FLAG_FOO est false et FLAG_BAR est true
  • FLAG_FOO est "false" et FLAG_BAR est "false"

Au lieu de modifier directement les valeurs des indicateurs, les annotations @DisableFlags et @EnableFlags les modifient en fonction des conditions des paramètres. Par exemple, legacyBarLogic ne s'exécute que lorsque FLAG_BAR est désactivé, ce qui se produit dans deux des quatre combinaisons d'indicateurs. legacyBarLogic est ignoré pour les deux autres combinaisons.

Il existe deux méthodes pour créer les paramétrisations de vos indicateurs :

  • FlagsParameterization.allCombinationsOf(String...) exécute 2^n exécutions de chaque test. Par exemple, un indicateur exécute 2x tests ou quatre indicateurs exécutent 16x tests.

  • FlagsParameterization.progressionOf(String...) exécute n+1 exécutions de chaque test. Par exemple, un indicateur exécute 2x tests et quatre indicateurs exécutent 5x indicateurs.

Créer des tests unitaires (C et C++)

AOSP inclut des macros de valeurs d'indicateurs pour les tests C et C++ écrits dans le framework GoogleTest.

  1. Dans votre source de test, incluez les définitions de macros et les bibliothèques générées par aconfig :

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. Dans votre source de test, au lieu d'utiliser TEST et TESTF macros pour vos scénarios de test, utilisez TEST_WITH_FLAGS et 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");
    }
    

    Où :

    • Les macros TEST_WITH_FLAGS et TEST_F_WITH_FLAGS sont utilisées à la place des macros TEST et TEST_F.
    • REQUIRES_FLAGS_ENABLED définit un ensemble d'indicateurs de lancement de fonctionnalités qui doivent répondre à la condition activée. Vous pouvez écrire ces indicateurs dans les macros ACONFIG_FLAG ou LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED définit un ensemble d'indicateurs de fonctionnalités qui doivent répondre à la condition désactivée. Vous pouvez écrire ces indicateurs dans les macros ACONFIG_FLAG ou LEGACY_FLAG.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) est une macro utilisée pour les indicateurs définis dans les fichiers aconfig. Cette macro accepte un espace de noms (TEST_NS) et un nom d'indicateur (readwrite_enabled_flag).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) est une macro utilisée pour les indicateurs définis par défaut dans la configuration de l'appareil.
  3. Dans votre fichier de compilation Android.bp, ajoutez les bibliothèques générées par aconfig et les bibliothèques de macros pertinentes en tant que dépendance de 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. Exécutez les tests en local avec cette commande :

    atest FlagMacrosTests
    

    Si l'indicateur my_namespace.android.myflag.tests.my_flag est désactivé, le résultat du test est le suivant :

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

    Si l'indicateur my_namespace.android.myflag.tests.my_flag est activé, le résultat du test est le suivant :

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

Créer des tests de bout en bout ou des tests unitaires lorsque les valeurs des indicateurs ne changent pas

Pour les scénarios de test dans lesquels vous ne pouvez pas remplacer les indicateurs et ne pouvez filtrer les tests que s'ils sont basés sur l'état actuel des indicateurs, utilisez la règle CheckFlagsRule avec les annotations RequiresFlagsEnabled et RequiresFlagsDisabled.

Les étapes suivantes vous montrent comment créer et exécuter un test de bout en bout ou un test unitaire dans lequel les valeurs des indicateurs ne peuvent pas être remplacées :

  1. Dans votre code de test, utilisez CheckFlagsRule pour appliquer le filtrage des tests. Utilisez également les annotations Java RequiresFlagsEnabled et RequiredFlagsDisabled pour spécifier les exigences d'indicateurs pour votre test.

    Le test côté appareil utilise 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() {}
    }
    

    Le test côté hôte utilise 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. Ajoutez jflag-unit et les bibliothèques générées par aconfig à la section static_libs du fichier de compilation de votre 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. Utilisez la commande suivante pour exécuter le test en local :

    atest FlagAnnotationTests
    

    Si l'indicateur Flags.FLAG_FLAG_NAME_1 est désactivé, le résultat du test est le suivant :

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

    Sinon, le résultat du test est le suivant :

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

Valeurs par défaut de l'appareil

SetFlagsRule initialisé utilise les valeurs des indicateurs de l'appareil. Si la valeur de l'indicateur sur l'appareil n'est pas remplacée, par exemple avec adb, la valeur par défaut est la même que la configuration de la version de la compilation. Si la valeur sur l'appareil a été remplacée, SetFlagsRule utilise la valeur de remplacement comme valeur par défaut.

Si le même test est exécuté sous différentes configurations de version, la valeur des indicateurs non explicitement définis avec SetFlagsRule peut varier.

Après chaque test, SetFlagsRule restaure l'instance FeatureFlags dans Flags à son FeatureFlagsImpl d'origine, afin qu'elle n'ait pas d'effets secondaires sur d'autres méthodes et classes de test.