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 oname
sempre definido comoflag-value
e ovalue
definido 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
FeatureFlagTargetPreparer
no 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_config
de 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
options
contém as substituições de flag com oname
sempre definido comoflag-value
evalue
definido 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
SetFlagsRule
com as anotações@EnableFlags
e@DisableFlags
para testar todas as ramificações de código. - Use o método
SetFlagsRule.ClassRule
para evitar bugs comuns de testes. - 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 à classeSetFlagsRule
.SetFlagsRule
é uma classe auxiliar fornecida para substituir valores de flags. Para entender comoSetFlagsRule
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
, 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
TEST
eTESTF
para seus casos de teste, useTEST_WITH_FLAGS
eTEST_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
eTEST_F_WITH_FLAGS
são usados em vez deTEST
eTEST_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 macrosACONFIG_FLAG
ouLEGACY_FLAG
.REQUIRES_FLAGS_DISABLED
define um conjunto de flags de recursos que precisam seguir a condição desativada. Você pode programar essas flags nas macrosACONFIG_FLAG
ouLEGACY_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 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:
No seu código de teste, use
CheckFlagsRule
para aplicar a filtragem. Além disso, use as anotaçõesRequiresFlagsEnabled
eRequiredFlagsDisabled
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() {} }
Adicione bibliotecas geradas por
jflag-unit
e aconfig à seçãostatic_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"], }
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.