Testar códigos em flags de lançamento de recursos

Com a introdução das flags de lançamento de recursos, você precisa aderir a novas políticas de teste:

  • Seus testes precisam abranger comportamentos ativados e desativados da flag.
  • Você precisa usar os mecanismos oficiais para definir os valores das flags durante os testes.
  • Os testes xTS não podem substituir os valores das flags nos testes.

A próxima seção mostra os mecanismos oficiais que precisam ser usados para aderir a essas políticas.

Testar seu código com flags

Cenário de teste Mecanismo usado
Testes locais quando os valores das flags mudam com frequência Android Debug Bridge, conforme discutido em Mudar o valor de uma flag durante a execução
Testes locais quando os valores das flags não mudam com frequência Arquivo de valores de flag, conforme discutido em Definir os valores de flags de lançamento de recursos
Testes de ponta a ponta em que há mudança nos valores das flags FeatureFlagTargetPreparer, conforme discutido em Criar testes de ponta a ponta
Testes de unidade em que há mudança nos valores das flags SetFlagsRule com @EnableFlags e @DisableFlags, conforme discutido em Criar testes de unidade (Java e Kotlin) ou Criar testes de unidade (C e C++)
Testes de unidade ou de ponta a ponta em que os valores das flags não podem mudar CheckFlagsRule, conforme discutido em Criar testes de unidade ou de ponta a ponta em que os valores das flags não mudam

Criar testes de ponta a ponta

O AOSP fornece uma classe chamada FeatureFlagTargetPreparer, que possibilita testes de ponta a ponta em um dispositivo. Essa classe aceita substituições de valores de flags como entrada, define essas flags na configuração dos dispositivos antes da execução do teste e restaura as flags após o fim do processo.

É possível aplicar a funcionalidade da classe FeatureFlagTargetPreparer nos níveis de módulo e configuração de teste.

Aplicar FeatureFlagTargetPreparer em uma configuração de módulo de teste

Para aplicar FeatureFlagTargetPreparer em uma configuração de módulo de teste, inclua FeatureFlagTargetPreparer e as substituições de valores da flag no arquivo de configuração 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>

Em que:

  • A target.preparer class é sempre definida como com.android.tradefed.targetprep.FeatureFlagTargetPreparer.
  • option é a substituição de flag com o name sempre definido como flag-value e o value definido como namespace/aconfigPackage.flagName=true|false.

Criar módulos de teste parametrizados com base em estados de flag

Para criar módulos de teste parametrizados com base em estados de flag, siga estas etapas:

  1. Inclua FeatureFlagTargetPreparer no arquivo de configuração de módulo de teste AndroidTest.xml:

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Especifique as opções de valor da flag na seção test_module_config de um arquivo de 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"},
        ],
    }
    

    O campo options contém as substituições de flag com o name sempre definido como flag-value e value definido como namespace/aconfigPackage.flagName=true|false.

Criar testes de unidade (Java e Kotlin)

Esta seção descreve a abordagem para substituir valores de flags aconfig nos níveis de classe e método (por teste) em testes em Java e Kotlin.

Para programar testes de unidade automatizados em uma base de código ampla com um grande número de flags, siga estas etapas:

  1. Use a classe SetFlagsRule com as anotações @EnableFlags e @DisableFlags para testar todas as ramificações de código.
  2. Use o método SetFlagsRule.ClassRule para evitar bugs comuns de testes.
  3. Use FlagsParameterization para testar suas classes em um conjunto amplo de configurações de flags.

Testar todas as ramificações de código

No caso de projetos que usam a classe estática para acessar flags, o sistema fornece a classe auxiliar SetFlagsRule para substituir os valores. O snippet de código abaixo mostra como incluir a SetFlagsRule e ativar diversas flags ao mesmo tempo:

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

Em que:

  • @Rule é uma anotação usada para adicionar a dependência de flags do JUnit à classe SetFlagsRule.
  • SetFlagsRule é uma classe auxiliar fornecida para substituir valores de flags. Para entender como SetFlagsRule determina os valores padrão, consulte Valores padrão do dispositivo.
  • @EnableFlags é uma anotação que aceita um número arbitrário de nomes de flags. Caso as flags estejam desativadas, use @DisableFlags. Você pode aplicar essas anotações a um método ou uma classe.

Defina valores de flags para todo o processo de teste, começando pela SetFlagsRule, que vem antes de qualquer método de configuração anotado com @Before no teste. Os valores das flags retornam ao estado anterior quando a SetFlagsRule termina o trabalho, o que acontece depois de qualquer método de configuração anotado com @After.

Garantir a definição correta das flags

Como já mencionado, a SetFlagsRule é usada com a anotação @Rule do JUnit, ou seja, a SetFlagsRule não garante que as flags sejam definidas corretamente durante o construtor de classe do teste nem em qualquer método anotado com @BeforeClass ou @AfterClass.

Para garantir que as correções de teste sejam construídas com o valor de classe correto, use o método SetFlagsRule.ClassRule. Assim, suas correções não serão criadas antes de um método de configuração anotado com @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() {
      ...
    }
  }

Ao adicionar a regra de classe SetFlagsRule.ClassRule, test_flag_foo_turned_on falha antes da execução quando o elemento FLAG_FLAG_FOO é lido pelo construtor da DemoClass.

Caso toda sua classe precise de uma flag ativada, mova a anotação @EnableFlags para o nível de classe (antes da declaração de classe). Isso permite que a SetFlagsRule.ClassRule garanta que a flag esteja definida corretamente durante o construtor de classe do teste ou durante qualquer método anotado com @BeforeClass ou @AfterClass.

Executar testes em várias configurações de flag

Como você pode definir valores de flag de acordo com o teste, também é possível usar parametrização para executar testes em diversas configurações de 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() {...}
}

Com a SetFlagsRule, mas sem parametrização, essa classe executa três testes (fooLogic, legacyBarLogic e newBarLogic). O método fooLogic é executado com qualquer definição dos valores de FLAG_FOO e FLAG_BAR no dispositivo.

Quando a parametrização é adicionada, o método FlagsParameterization.allCombinationsOf cria todas as combinações possíveis das flags 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.

Em vez de mudar os valores das flags diretamente, as anotações @DisableFlags e @EnableFlags fazem modificações com base em condições de parâmetros. Por exemplo, a legacyBarLogic é executada apenas quando a FLAG_BAR está desativada, o que acontece em duas das quatro combinações de flags. A legacyBarLogic é ignorada nas outras duas combinações.

Há dois métodos para criar as parametrizações para suas flags:

  • FlagsParameterization.allCombinationsOf(String...) realiza 2^n execuções de cada teste. Por exemplo, uma flag executa 2 testes ou quatro flags executam 16 testes.

  • FlagsParameterization.progressionOf(String...) realiza n+1 execuções de cada teste. Por exemplo, uma flag executa 2 testes, e quatro flags executam 5 testes.

Criar testes de unidade (C e C++)

O AOSP inclui macros de valores de flags para testes em C e C++ programados no sistema GoogleTest.

  1. Na sua origem de teste, inclua as definições dos macros e as bibliotecas geradas pela aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. Na sua origem de teste, em vez de usar macros TEST e TESTF para seus casos de teste, use 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");
    }
    

    Em que:

    • Os macros TEST_WITH_FLAGS e TEST_F_WITH_FLAGS são usados em vez de TEST e TEST_F.
    • REQUIRES_FLAGS_ENABLED define um conjunto de flags de lançamento de recursos que precisam seguir a condição ativada. Você pode programar essas flags nas macros ACONFIG_FLAG ou LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED define um conjunto de flags de recursos que precisam seguir a condição desativada. Você pode programar essas flags nas macros ACONFIG_FLAG ou LEGACY_FLAG.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) é uma macro usada em flags definidas em arquivos aconfig. Ela aceita um namespace (TEST_NS) e um nome de flag (readwrite_enabled_flag).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) é uma macro usada em flags definidas na configuração do dispositivo por padrão.
  3. No seu arquivo de build Android.bp, adicione as bibliotecas geradas por aconfig e as bibliotecas de macro relevantes como uma dependência de teste:

    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. Execute os testes localmente com este comando:

    atest FlagMacrosTests
    

    Se a flag my_namespace.android.myflag.tests.my_flag estiver desativada, o resultado do teste será:

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

    Se a flag my_namespace.android.myflag.tests.my_flag estiver ativada, o resultado do teste será:

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

Criar testes de unidade ou de ponta a ponta em que os valores das flags não mudam

Para casos de teste em que não é possível substituir as flags e você pode filtrar testes apenas quando eles são baseados no estado atual da flag, use a regra CheckFlagsRule com as anotações RequiresFlagsEnabled e RequiresFlagsDisabled.

As etapas abaixo mostram como criar e executar um teste de unidade ou de ponta a ponta em que os valores da flag não podem ser substituídos:

  1. No seu código de teste, use CheckFlagsRule para aplicar a filtragem. Além disso, use as anotações RequiresFlagsEnabled e RequiredFlagsDisabled em Java para especificar os requisitos da flag para o teste.

    O teste do lado do dispositivo usa a 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() {}
    }
    

    O teste do lado do host usa a 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. Adicione bibliotecas geradas por jflag-unit e aconfig à seção static_libs do arquivo de build para o teste:

    android_test {
        name: "FlagAnnotationTests",
        srcs: ["*.java"],
        static_libs: [
            "androidx.test.rules",
            "my_aconfig_lib",
            "flag-junit",
            "platform-test-annotations",
        ],
        test_suites: ["general-tests"],
    }
    
  3. Use este comando para executar o teste localmente:

    atest FlagAnnotationTests
    

    Se a flag Flags.FLAG_FLAG_NAME_1 estiver desativada, o resultado do teste será:

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

    Do contrário, o resultado do teste será:

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

Valores padrão do dispositivo

A SetFlagsRule inicializada usa valores de flags do dispositivo. Se o valor da flag no dispositivo não for substituído, como com o adb, o valor padrão será o mesmo da configuração de lançamento do build. Se o valor no dispositivo tiver sido substituído, a SetFlagsRule usará o novo valor como o padrão.

Se o mesmo teste for executado em uma configuração de lançamento diferente, o valor das flags que não foram definidas explicitamente com SetFlagsRule poderá variar.

Depois de cada teste, a SetFlagsRule vai restaurar a instância FeatureFlags nas Flags para o valor FeatureFlagsImpl original. Assim, ela não terá efeitos colaterais em outros métodos e classes de teste.