Testar o código em flags de lançamento de recursos

Com a introdução de sinalizações de lançamento de recursos, há novas políticas de teste que você precisa seguir:

  • Seus testes precisam abranger os comportamentos ativado e desativado da sinalização.
  • Você deve usar os mecanismos oficiais para definir os valores dos sinalizadores durante os testes.
  • Os testes xTS não devem substituir os valores de flag nos testes.

A próxima seção apresenta os mecanismos oficiais que você precisa usar para aderir a essas políticas.

Testar o código sinalizado

Cenário de teste Mecanismo usado
Teste local quando os valores de sinalização 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 de flag não mudam com frequência Arquivo de valores de sinalização, conforme discutido em Definir valores de sinalização de inicialização de recurso
Testes completos em que os valores de sinalização mudam FeatureFlagTargetPreparer, conforme discutido em Criar testes completos.
Teste de unidade em que os valores das sinalizações mudam SetFlagsRule com @EnableFlags e @DisableFlags, conforme discutido em Criar testes de unidade (Java e Kotlin) ou Criar testes de unidade (C e C++)
Teste completo ou de unidade em que os valores de sinalização não podem ser alterados CheckFlagsRule, conforme discutido em Criar testes de unidade ou completos em que os valores de flag não mudam

Criar testes completos

O AOSP oferece uma classe chamada FeatureFlagTargetPreparer, que permite fazer testes completos em um dispositivo. Essa classe aceita substituições de valor de flag como entrada, define essas flags na configuração dos dispositivos antes da execução do teste e restaura as flags após a execução.

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

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

Para aplicar FeatureFlagTargetPreparer a uma configuração de módulo de teste, inclua FeatureFlagTargetPreparer e as substituições de valor de sinalização no arquivo de configuração do módulo de teste 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:

  • target.preparer class é sempre definido como com.android.tradefed.targetprep.FeatureFlagTargetPreparer.
  • option é a substituição da flag com name sempre definido como flag-value e 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 nos estados de indicador:

  1. Inclua FeatureFlagTargetPreparer no arquivo de configuração do 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 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 os valores da flag aconfig no nível de classe e método (por teste) em testes Java e Kotlin.

Para escrever testes de unidade automatizados em uma base de código grande 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 teste.
  3. Use FlagsParameterization para testar suas classes em um amplo conjunto de configurações de sinalizadores.

Testar todas as ramificações de código

Para projetos que usam a classe estática para acessar flags, a classe auxiliar SetFlagsRule é fornecida para substituir os valores das sinalizações. O snippet de código a seguir mostra como incluir o SetFlagsRule e ativar várias flags de uma só 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() {
    ...
    }

Em que:

  • @Rule é uma anotação usada para adicionar a dependência flag-JUnit da classe SetFlagsRule.
  • SetFlagsRule é uma classe auxiliar fornecida para substituir valores de sinalização. Para informações sobre como SetFlagsRule determina valores padrão, consulte Valores padrão do dispositivo.
  • @EnableFlags é uma anotação que aceita um número arbitrário de nomes de sinalizações. Ao desativar as flags, use @DisableFlags. É possível aplicar essas anotações a um método ou uma classe.

Defina valores de sinalização para todo o processo de teste, começando com o SetFlagsRule, que é anterior a qualquer método de configuração com anotação @Before no teste. Os valores de sinalização retornam ao estado anterior quando a SetFlagsRule é concluída, ou seja, depois de todos os métodos de configuração com anotação @After.

Verifique se as flags estão definidas corretamente

Como mencionado anteriormente, SetFlagsRule é usado com a anotação @Rule do JUnit, o que significa que o SetFlagsRule não pode garantir que as sinalizações sejam definidas corretamente durante o construtor da classe de teste ou em qualquer método com anotação @BeforeClass ou @AfterClass.

Para garantir que os recursos de teste sejam construídos com o valor de classe correto, use o método SetFlagsRule.ClassRule para que eles não sejam criados até que um método de configuração com anotação @Before seja usado:

  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, o test_flag_foo_turned_on falhará antes de ser executado quando FLAG_FLAG_FOO é lido pelo construtor de DemoClass.

Se toda a classe precisar de uma flag ativada, mova a anotação @EnableFlags para o nível da classe (antes da declaração de classe). Mover a anotação para o nível da classe permite que SetFlagsRule.ClassRule garanta que a sinalização seja definida corretamente durante o construtor da classe de teste ou durante qualquer método com anotação @BeforeClass ou @AfterClass.

Executar testes em várias configurações de sinalização

Como é possível definir valores de sinalização para cada teste, também é possível usar a parametrização para executar testes em várias configurações de sinalização:

...
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 SetFlagsRule, mas sem a parametrização, essa classe executa três testes (fooLogic, legacyBarLogic e newBarLogic). O método fooLogic é executado com quaisquer valores de FLAG_FOO e FLAG_BAR definidos no dispositivo.

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

Em vez de mudar diretamente os valores das flags, as anotações @DisableFlags e @EnableFlags modificam os valores com base nas condições do parâmetro. Por exemplo, legacyBarLogic é executado somente quando FLAG_BAR está desativado, o que ocorre em duas das quatro combinações de sinalizações. O legacyBarLogic é ignorado para as outras duas combinações.

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

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

  • FlagsParameterization.progressionOf(String...) executa n+1 execuções de cada teste. Por exemplo, uma sinalização executa dois testes e quatro outras executam cinco vezes.

Criar testes de unidade (C e C++)

O AOSP inclui macros de valor de flag para testes C e C++ escritos no framework GoogleTest.

  1. Na origem de teste, inclua as definições de macro e as bibliotecas geradas pelo aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. Na origem do teste, em vez de usar as macros TEST e TESTF nos 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:

    • As macros TEST_WITH_FLAGS e TEST_F_WITH_FLAGS são usadas em vez das macros TEST e TEST_F.
    • REQUIRES_FLAGS_ENABLED define um conjunto de flags de lançamento de recursos que precisam atender à condição ativada. É possível gravar essas sinalizações nas macros ACONFIG_FLAG ou LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED define um conjunto de sinalizações de recursos que precisam atender à condição desativada. É possível gravar essas flags em macros ACONFIG_FLAG ou LEGACY_FLAG.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) é uma macro usada para sinalizações definidas em arquivos aconfig. Essa macro 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 para sinalizações definidas na configuração do dispositivo por padrão.
  3. No arquivo de build Android.bp, adicione as bibliotecas geradas pelo aconfig e as bibliotecas de macros 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 sinalização 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 completos ou de unidade em que os valores das sinalizações não mudam

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

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

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

    O teste no 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 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 jflag-unit e bibliotecas geradas pelo 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 o seguinte 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)
    

    Caso contrário, o resultado 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

O SetFlagsRule inicializado usa valores de sinalização 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 que a configuração de lançamento do build. Se o valor no dispositivo tiver sido substituído, o SetFlagsRule vai usar o valor de substituição como padrão.

Se o mesmo teste for executado em diferentes configurações de lançamento, o valor das flags não definidas explicitamente com SetFlagsRule poderá variar.

Depois de cada teste, SetFlagsRule restaura a instância FeatureFlags em Flags para o FeatureFlagsImpl original, para que ela não tenha efeitos colaterais em outros métodos e classes de teste.