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 comocom.android.tradefed.targetprep.FeatureFlagTargetPreparer. optioné a substituição de flag com onamesempre definido comoflag-valuee ovaluedefinido comonamespace/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:
Inclua
FeatureFlagTargetPreparerno arquivo de configuração de módulo de testeAndroidTest.xml:<target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >Especifique as opções de valor da flag na seção
test_module_configde um arquivo de buildAndroid.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
optionscontém as substituições de flag com onamesempre definido comoflag-valueevaluedefinido comonamespace/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:
- Use a classe
SetFlagsRulecom as anotações@EnableFlagse@DisableFlagspara testar todas as ramificações de código. - Use o método
SetFlagsRule.ClassRulepara evitar bugs comuns de testes. - Use
FlagsParameterizationpara 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 à classeSetFlagsRule.SetFlagsRuleé uma classe auxiliar fornecida para substituir valores de flags. Para entender comoSetFlagsRuledetermina 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, eFLAG_BARétrue.FLAG_FOOétrue, eFLAG_BARéfalse.FLAG_FOOéfalse, eFLAG_BARétrue.FLAG_FOOé false, eFLAG_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.
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"Na sua origem de teste, em vez de usar macros
TESTeTESTFpara seus casos de teste, useTEST_WITH_FLAGSeTEST_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_FLAGSeTEST_F_WITH_FLAGSsão usados em vez deTESTeTEST_F. REQUIRES_FLAGS_ENABLEDdefine um conjunto de flags de lançamento de recursos que precisam seguir a condição ativada. Você pode programar essas flags nas macrosACONFIG_FLAGouLEGACY_FLAG.REQUIRES_FLAGS_DISABLEDdefine um conjunto de flags de recursos que precisam seguir a condição desativada. Você pode programar essas flags nas macrosACONFIG_FLAGouLEGACY_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.
- Os macros
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"], ... }Execute os testes localmente com este comando:
atest FlagMacrosTestsSe a flag
my_namespace.android.myflag.tests.my_flagestiver 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_flagestiver 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:
No seu código de teste, use
CheckFlagsRulepara aplicar a filtragem. Além disso, use as anotaçõesRequiresFlagsEnabledeRequiredFlagsDisabledem 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() {} }Adicione bibliotecas geradas por
jflag-unite aconfig à seçãostatic_libsdo 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"], }Use este comando para executar o teste localmente:
atest FlagAnnotationTestsSe a flag
Flags.FLAG_FLAG_NAME_1estiver 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.