Cómo probar el código dentro de las marcas de lanzamiento de funciones

Con la incorporación de las marcas de lanzamiento de funciones, hay nuevas políticas de pruebas con las que debes cumplir:

  • Tus pruebas deben abarcar los comportamientos inhabilitados y habilitados de la marca.
  • Debes usar los mecanismos oficiales para establecer valores de las marcas durante las pruebas.
  • Las pruebas de xTS no deben anular los valores de las marcas en las pruebas.

En la siguiente sección, se proporcionan los mecanismos oficiales que debes usar para cumplir con estas políticas.

Cómo probar el código con marcas

Escenario de prueba Mecanismo utilizado
Pruebas locales cuando los valores de las marcas cambian a menudo Android Debug Bridge, como se menciona en Cómo cambiar el valor de una marca durante el tiempo de ejecución
Pruebas locales cuando los valores de las marcas no cambian a menudo Valores de marcas, como se menciona en Cómo establecer valores de marcas de lanzamiento de funciones
Pruebas de extremo a extremo en las que cambian los valores de las marcas FeatureFlagTargetPreparer, como se menciona en Cómo crear pruebas de extremo a extremo
Pruebas de unidades en las que cambian los valores de las marcas SetFlagsRule con @EnableFlags y @DisableFlags, como se menciona en Cómo crear pruebas de unidades (Java y Kotlin) o Cómo crear pruebas de unidades (C y C++)
Pruebas de unidades o de extremo a extremo en las que no pueden cambiar los valores de las marcas CheckFlagsRule, como se menciona en Cómo crear pruebas de unidades o de extremo a extremo en las que no cambian los valores de las marcas

Cómo crear pruebas de extremo a extremo

AOSP proporciona una clase llamada FeatureFlagTargetPreparer, que habilita las pruebas de extremo a extremo en un dispositivo. Esta clase acepta anulaciones de los valores de las marcas como entrada, establece esas marcas en la configuración del dispositivo antes de la ejecución de prueba y restablece las marcas después de la ejecución.

Puedes aplicar la funcionalidad de la clase FeatureFlagTargetPreparer en el módulo de prueba y probar los niveles de configuración.

Cómo aplicar FeatureFlagTargetPreparer en la configuración de un módulo de prueba

Para aplicar FeatureFlagTargetPreparer en la configuración de un módulo de prueba, incluye FeatureFlagTargetPreparer y anulaciones de los valores de las marcas en el archivo de configuración del módulo de prueba 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>

En la que:

  • target.preparer class siempre se configura en com.android.tradefed.targetprep.FeatureFlagTargetPreparer.
  • option es la anulación de la marca con name siempre configurado en flag-value y value configurado en namespace/aconfigPackage.flagName=true|false.

Cómo crear módulos de prueba parametrizados basados en estados de marcas

Para crear módulos de prueba parametrizados basados en estados de marcas, haz lo siguiente:

  1. Incluye FeatureFlagTargetPreparer en el archivo de configuración del módulo de prueba AndroidTest.xml:

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Especifica opciones de valores de marcas en la sección test_module_config de un archivo de compilación 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"},
        ],
    }
    

    El campo options contiene anulaciones de marcas con name siempre configurado en flag-value y value configurado en namespace/aconfigPackage.flagName=true|false.

Cómo crear pruebas de unidades (Java y Kotlin)

En esta sección, se describe la forma de anular valores de marcas de aconfig en el nivel de clase y método (por prueba) en pruebas de Java y Kotlin.

Para escribir pruebas de unidades automatizadas en una base de código grande con una gran cantidad de marcas, sigue estos pasos:

  1. Usa la clase SetFlagsRule con las anotaciones @EnableFlags y @DisableFlags para probar todas las ramas de código.
  2. Usa el método SetFlagsRule.ClassRule para evitar errores comunes en las pruebas.
  3. Usa FlagsParameterization para probar tus clases en un amplio conjunto de configuraciones de marcas.

Cómo probar todas las ramas de código

En el caso de los proyectos que usan la clase estática para acceder a las marcas, se proporciona la clase de ayuda SetFlagsRule para anular valores de las marcas. En el siguiente fragmento de código, se muestra cómo incluir SetFlagsRule y habilitar varias marcas a la vez:

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

En la que:

  • @Rule es una anotación que se utiliza para agregar la dependencia flag-JUnit de la clase SetFlagsRule.
  • SetFlagsRule es la clase de ayuda que se proporciona para anular valores de marcas. Si quieres obtener más información sobre el modo que SetFlagsRule determina valores predeterminados, consulta Valores predeterminados del dispositivo.
  • @EnableFlags es una anotación que acepta un número arbitrario de nombres de marcas. Cuando inhabilites marcas, usa @DisableFlags. Puedes aplicar estas anotaciones a un método o una clase.

Establece valores de marcas para todo el proceso de pruebas, comenzando con SetFlagsRule, que es anterior a cualquier método de configuración anotado con @Before en la prueba. Los valores de las marcas regresan al estado anterior cuando termina SetFlagsRule, que es posterior a cualquier método de configuración anotado con @After.

Cómo garantizar que las marcas estén configuradas correctamente

Como ya se mencionó, SetFlagsRule se usa con la anotación @Rule de JUnit, lo que significa que SetFlagsRule no puede garantizar que tus marcas estén configuradas correctamente durante el constructor de la clase de prueba o cualquier método anotado con @BeforeClass o @AfterClass.

Para asegurarte de que los accesorios de prueba se construyan con el valor de clase correcto, usa el método SetFlagsRule.ClassRule para que estos no se creen hasta que haya un método de configuración anotado 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() {
      ...
    }
  }

Si agregas la regla de clase SetFlagsRule.ClassRule, test_flag_foo_turned_on fallará antes de ejecutarse cuando el constructor de DemoClass lea FLAG_FLAG_FOO.

Si toda la clase necesita tener una marca habilitada, mueve la anotación @EnableFlags al nivel de clase (antes de la declaración de la clase). Mover la anotación al nivel de clase permite que SetFlagsRule.ClassRule garantice que la marca está configurada correctamente durante el constructor de la clase de prueba o cualquier método anotado con @AfterClass o @BeforeClass.

Cómo ejecutar pruebas en múltiples configuraciones de marcas

Dado que puedes establecer valores de marcas prueba por prueba, también puedes usar la parametrización para ejecutar pruebas en múltiples configuraciones de marcas:

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

Ten en cuenta que, con SetFlagsRule, pero sin parametrización, esta clase ejecuta tres pruebas (fooLogic, legacyBarLogic y newBarLogic). El método fooLogic se ejecuta con los valores de FLAG_FOO y FLAG_BAR que estén configurados en el dispositivo.

Cuando se agrega la parametrización, el método FlagsParameterization.allCombinationsOf crea todas las combinaciones posibles de las marcas FLAG_FOO y FLAG_BAR:

  • FLAG_FOO es true y FLAG_BAR es true.
  • FLAG_FOO es true y FLAG_BAR es false.
  • FLAG_FOO es false y FLAG_BAR es true.
  • FLAG_FOO es false y FLAG_BAR es false.

En lugar de cambiar directamente los valores de las marcas, las anotaciones @DisableFlags y @EnableFlags modifican los valores en función de las condiciones del parámetro. Por ejemplo, legacyBarLogic se ejecuta solo cuando se inhabilita FLAG_BAR, lo que ocurre en dos de las cuatro combinaciones de marcas. legacyBarLogic se omite para las otras dos combinaciones.

Existen dos métodos para crear la parametrización de tus marcas:

  • FlagsParameterization.allCombinationsOf(String...) ejecuta 2^n instancias de cada prueba. Por ejemplo, una marca ejecuta pruebas 2 veces o cuatro marcas ejecutan pruebas 16 veces.

  • FlagsParameterization.progressionOf(String...) ejecuta n+1 instancias de cada prueba. Por ejemplo, una marca ejecuta pruebas 2 veces y cuatro marcas ejecutan pruebas 5 veces.

Cómo crear pruebas de unidades (C y C++)

AOSP incluye macros de valores de marcas para pruebas en C y C++ escritas en el framework de GoogleTest.

  1. En la fuente de prueba, incluye las definiciones de la macro y las bibliotecas generadas por aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. En la fuente de prueba, en lugar de usar las macros TEST y TESTF para los casos de prueba, usa TEST_WITH_FLAGS y 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");
    }
    

    En la que:

    • Se usan las macros TEST_WITH_FLAGS y TEST_F_WITH_FLAGS en lugar de las macros TEST y TEST_F macros.
    • REQUIRES_FLAGS_ENABLED define un conjunto de marcas de lanzamiento de funciones que deben cumplir con la condición de habilitadas. Puedes escribir estas marcas en las macros ACONFIG_FLAG o LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED define un conjunto de marcas de función que deben cumplir con la condición de inhabilitadas. Puedes escribir estas marcas en las macros ACONFIG_FLAG o LEGACY_FLAG.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) es una macro que se utiliza para las marcas definidas en archivos de aconfig. Esta macro acepta un espacio de nombres (TEST_NS) y un nombre de marca (readwrite_enabled_flag).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) es una macro que se utiliza para las marcas definidas en la configuración del dispositivo de forma predeterminada.
  3. En tu archivo de compilación Android.bp, agrega las bibliotecas generadas por aconfig y las bibliotecas de macros pertinentes como una dependencia de prueba:

    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. Ejecuta las pruebas de manera local con este comando:

    atest FlagMacrosTests
    

    Si la marca my_namespace.android.myflag.tests.my_flag está inhabilitada, el resultado de la prueba es el siguiente:

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

    Si la marca my_namespace.android.myflag.tests.my_flag está habilitada, el resultado de la prueba es el siguiente:

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

Cómo crear pruebas de unidades o de extremo a extremo en las que no cambian los valores de las marcas

En los casos de prueba en los que no puedes anular marcas y puedes filtrar pruebas solo si se basan en el estado actual de la marca, usa la regla CheckFlagsRule con las anotaciones RequiresFlagsEnabled y RequiresFlagsDisabled.

Los pasos siguientes te muestran cómo crear y ejecutar pruebas de unidades o de extremo a extremo en las que los valores de las marcas no se pueden anular:

  1. En el código de prueba, usa CheckFlagsRule para aplicar filtros de prueba. Además, usa las anotaciones Java RequiresFlagsEnabled y RequiredFlagsDisabled para especificar los requisitos de las marcas para tu prueba.

    La prueba del lado del dispositivo usa la clase 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() {}
    }
    

    La prueba del lado del host usa la clase 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. Agrega jflag-unit y las bibliotecas generadas por aconfig a la sección static_libs del archivo de compilación para tu prueba:

    android_test {
        name: "FlagAnnotationTests",
        srcs: ["*.java"],
        static_libs: [
            "androidx.test.rules",
            "my_aconfig_lib",
            "flag-junit",
            "platform-test-annotations",
        ],
        test_suites: ["general-tests"],
    }
    
  3. Usa el siguiente comando para ejecutar la prueba de forma local:

    atest FlagAnnotationTests
    

    Si la marca Flags.FLAG_FLAG_NAME_1 está inhabilitada, el resultado de la prueba será el siguiente:

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

    De lo contrario, el resultado será:

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

Valores predeterminados del dispositivo

La SetFlagsRule inicializada usa valores de marcas del dispositivo. Si el valor de la marca en el dispositivo no se anula, como con adb, el valor predeterminado es el mismo que el de la configuración de versión de la compilación. Si se anuló el valor en el dispositivo, SetFlagsRule usa el valor de anulación como predeterminado.

Si se ejecuta la misma prueba bajo configuraciones de versión diferentes, el valor de las marcas que no se estableció de manera explícita con SetFlagsRule puede variar.

Después de cada prueba, SetFlagsRule restablece la instancia de FeatureFlags en Flags a su FeatureFlagsImpl original para que no genere efectos secundarios en otros métodos y clases de prueba.