Wprowadziliśmy flagi uruchamiania funkcji, co wiąże się z nowymi zasadami testowania, których musisz przestrzegać:
- Testy muszą obejmować zarówno włączone, jak i wyłączone zachowania flagi.
- Podczas testowania musisz używać oficjalnych mechanizmów do ustawiania wartości flag.
- Testy xTS nie powinny zastępować wartości flag w testach.
W następnej sekcji znajdziesz oficjalne mechanizmy, których musisz używać, aby przestrzegać tych zasad.
Testowanie oznaczonego kodu
Scenariusz testu | Użyty mechanizm |
---|---|
Testowanie lokalne, gdy wartości flagi często się zmieniają | Android Debug Bridge, zgodnie z opisem w artykule Zmiana wartości flagi w czasie działania. |
Testowanie lokalne, gdy wartości flagi nie zmieniają się często | Plik wartości flag, o którym mowa w sekcji Ustawianie wartości flag uruchamiania funkcji. |
Testowanie kompleksowe, w którym zmieniają się wartości flagi | FeatureFlagTargetPreparer – zgodnie z opisem w artykule Tworzenie testów kompleksowych. |
Testowanie jednostkowe, w którym zmieniają się wartości flagi | SetFlagsRule z @EnableFlags i @DisableFlags , jak opisano w artykule Tworzenie testów jednostkowych (Java i Kotlin) lub Tworzenie testów jednostkowych (C i C++). |
Testy kompleksowe lub jednostkowe, w których wartości flag nie mogą się zmieniać | CheckFlagsRule zgodnie z opisem w artykule Tworzenie testów kompleksowych lub jednostkowych, w których wartości flag nie ulegają zmianie. |
Tworzenie testów kompleksowych
AOSP udostępnia klasę o nazwie FeatureFlagTargetPreparer
, która umożliwia kompleksowe testowanie na urządzeniu. Ta klasa akceptuje zastąpienia wartości flag jako dane wejściowe, ustawia te flagi w konfiguracji urządzenia przed wykonaniem testu i przywraca flagi po wykonaniu.
Funkcjonalność klasy FeatureFlagTargetPreparer
możesz zastosować na poziomie modułu testowego i konfiguracji testu.
Zastosuj FeatureFlagTargetPreparer w konfiguracji modułu testowego
Aby zastosować FeatureFlagTargetPreparer
w konfiguracji modułu testowego, w pliku konfiguracji modułu testowego AndroidTest.xml
umieść FeatureFlagTargetPreparer
i zastąpienia wartości flagi:
<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>
Gdzie:
target.preparer class
ma zawsze wartośćcom.android.tradefed.targetprep.FeatureFlagTargetPreparer
.option
to zastąpienie flagi, w którymname
jest zawsze ustawione naflag-value
, avalue
nanamespace/aconfigPackage.flagName=true|false
.
Tworzenie sparametryzowanych modułów testowych na podstawie stanów flag
Aby utworzyć sparametryzowane moduły testowe na podstawie stanów flag:
Dołącz
FeatureFlagTargetPreparer
do pliku konfiguracjiAndroidTest.xml
modułu testowego:<target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
Określ opcje wartości flagi w sekcji
test_module_config
pliku kompilacjiAndroid.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"}, ], }
Pole
options
zawiera zastąpienia flagi, przy czymname
jest zawsze ustawione naflag-value
, avalue
nanamespace/aconfigPackage.flagName=true|false
.
Tworzenie testów jednostkowych (Java i Kotlin)
W tej sekcji opisujemy, jak zastępować wartości flagi aconfig na poziomie klasy i metody (w przypadku poszczególnych testów) w testach w językach Java i Kotlin.
Aby napisać automatyczne testy jednostkowe w dużej bazie kodu z wieloma flagami, wykonaj te czynności:
- Użyj klasy
SetFlagsRule
z adnotacjami@EnableFlags
i@DisableFlags
, aby przetestować wszystkie gałęzie kodu. - Aby uniknąć typowych błędów w testach, używaj metody
SetFlagsRule.ClassRule
. - Użyj
FlagsParameterization
, aby przetestować klasy w szerokim zakresie konfiguracji flag.
Testowanie wszystkich gałęzi kodu
W przypadku projektów, które używają klasy statycznej do uzyskiwania dostępu do flag, udostępniana jest klasa pomocnicza SetFlagsRule
, która umożliwia zastępowanie wartości flag. Poniższy fragment kodu pokazuje, jak uwzględnić SetFlagsRule
i włączyć kilka flag jednocześnie:
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() {
...
}
Gdzie:
@Rule
to adnotacja używana do dodawania zależności flag-JUnit klasySetFlagsRule
.SetFlagsRule
to klasa pomocnicza, która umożliwia zastępowanie wartości flag. Informacje o tym, jakSetFlagsRule
określa wartości domyślne, znajdziesz w sekcji Domyślne wartości urządzenia.@EnableFlags
to adnotacja, która akceptuje dowolną liczbę nazw flag. Podczas wyłączania flag używaj znaku@DisableFlags
. Te adnotacje możesz stosować do metody lub klasy.
Ustaw wartości flag dla całego procesu testowania, zaczynając od SetFlagsRule
, czyli przed wszystkimi metodami konfiguracji oznaczonymi adnotacją @Before
w teście. Gdy funkcja SetFlagsRule
zakończy działanie (czyli po wszystkich metodach konfiguracji oznaczonych adnotacją @After
), wartości flag wracają do poprzedniego stanu.
Sprawdź, czy flagi są prawidłowo ustawione.
Jak wspomnieliśmy wcześniej, adnotacja SetFlagsRule
jest używana z adnotacją JUnit @Rule
, co oznacza, że SetFlagsRule
nie może zagwarantować, że flagi są prawidłowo ustawione w konstruktorze klasy testowej ani w metodach oznaczonych adnotacjami @BeforeClass
lub @AfterClass
.
Aby mieć pewność, że testowe elementy stałe są tworzone z prawidłową wartością klasy, użyj metody SetFlagsRule.ClassRule
, aby elementy stałe nie były tworzone do czasu wywołania metody konfiguracji z adnotacją @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() {
...
}
}
Po dodaniu reguły klasy SetFlagsRule.ClassRule
funkcja test_flag_foo_turned_on
kończy się niepowodzeniem przed uruchomieniem, gdy konstruktor DemoClass
odczytuje wartość FLAG_FLAG_FOO
.
Jeśli cała klasa wymaga włączenia flagi, przenieś adnotację @EnableFlags
na poziom klasy (przed deklaracją klasy). Przeniesienie adnotacji na poziom klasy pozwala SetFlagsRule.ClassRule
zapewnić prawidłowe ustawienie flagi
w konstruktorze klasy testowej lub w dowolnych metodach oznaczonych adnotacjami @BeforeClass
lub @AfterClass
.
Przeprowadzanie testów w różnych konfiguracjach flag
Wartości flag można ustawiać dla każdego testu z osobna, więc możesz też używać parametryzacji do przeprowadzania testów w różnych konfiguracjach 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() {...}
}
Pamiętaj, że w przypadku SetFlagsRule
, ale bez parametryzacji, ta klasa uruchamia 3 testy (fooLogic
, legacyBarLogic
i newBarLogic
). Metoda fooLogic
jest uruchamiana z wartościami FLAG_FOO
i FLAG_BAR
ustawionymi na urządzeniu.
Po dodaniu parametryzacji metoda FlagsParameterization.allCombinationsOf
tworzy wszystkie możliwe kombinacje flag FLAG_FOO
i FLAG_BAR
:
FLAG_FOO
totrue
, aFLAG_BAR
totrue
FLAG_FOO
totrue
, aFLAG_BAR
tofalse
FLAG_FOO
tofalse
, aFLAG_BAR
totrue
FLAG_FOO
ma wartość fałsz, aFLAG_BAR
ma wartośćfalse
Zamiast bezpośrednio zmieniać wartości flag, adnotacje @DisableFlags
i @EnableFlags
modyfikują wartości flag na podstawie warunków parametrów. Na przykład legacyBarLogic
działa tylko wtedy, gdy FLAG_BAR
jest wyłączona, co ma miejsce w 2 z 4 kombinacji flag. W przypadku pozostałych 2 kombinacji legacyBarLogic
jest pomijany.
Parametryzację flag można utworzyć na 2 sposoby:
FlagsParameterization.allCombinationsOf(String...)
wykonuje 2^n uruchomień każdego testu. Na przykład 1 flaga uruchamia testy 2x, a 4 flagi – testy 16x.FlagsParameterization.progressionOf(String...)
wykonuje n+1 uruchomień każdego testu. Na przykład 1 flaga uruchamia testy 2x, a 4 flagi uruchamiają testy 5x.
Tworzenie testów jednostkowych (C i C++)
AOSP zawiera makra wartości flag dla testów w językach C i C++ napisanych w ramach GoogleTest.
W źródle testowym uwzględnij definicje makr i biblioteki wygenerowane przez aconfig:
#include <flag_macros.h> #include "android_cts_flags.h"
W źródle testowym zamiast makr
TEST
iTESTF
w przypadkach testowych użyj makrTEST_WITH_FLAGS
iTEST_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"); }
Gdzie:
- Zamiast makr
TEST
iTEST_F
używane są makraTEST_WITH_FLAGS
iTEST_F_WITH_FLAGS
. REQUIRES_FLAGS_ENABLED
definiuje zestaw flag udostępniania funkcji, które muszą spełniać warunek włączenia. Możesz wpisać te flagi w makrachACONFIG_FLAG
lubLEGACY_FLAG
.REQUIRES_FLAGS_DISABLED
definiuje zestaw flag funkcji, które muszą spełniać warunek wyłączenia. Możesz wpisać te flagi w makrachACONFIG_FLAG
lubLEGACY_FLAG
.ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag)
to makro używane w przypadku flag zdefiniowanych w plikach konfiguracyjnych. To makro akceptuje przestrzeń nazw (TEST_NS
) i nazwę flagi (readwrite_enabled_flag
).LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag)
to makro używane w przypadku flag ustawionych domyślnie w konfiguracji urządzenia.
- Zamiast makr
W pliku kompilacji
Android.bp
dodaj wygenerowane przez aconfig biblioteki i odpowiednie biblioteki makr jako zależność testową: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"], ... }
Uruchom testy lokalnie za pomocą tego polecenia:
atest FlagMacrosTests
Jeśli flaga
my_namespace.android.myflag.tests.my_flag
jest wyłączona, wynik testu to:[1/2] MyTest#test1: IGNORED (0ms) [2/2] MyTestF#test2: PASSED (0ms)
Jeśli flaga
my_namespace.android.myflag.tests.my_flag
jest włączona, wynik testu to:[1/2] MyTest#test1: PASSED (0ms) [2/2] MyTestF#test2: IGNORED (0ms)
Tworzenie testów kompleksowych lub jednostkowych, w których wartości flag nie ulegają zmianie
W przypadku testów, w których nie można zastąpić flag i które można filtrować tylko wtedy, gdy są oparte na bieżącym stanie flagi, użyj reguły CheckFlagsRule
z adnotacjami RequiresFlagsEnabled
i RequiresFlagsDisabled
.
Poniżej znajdziesz instrukcje tworzenia i uruchamiania testu kompleksowego lub jednostkowego, w którym nie można zastąpić wartości flag:
W kodzie testowym użyj
CheckFlagsRule
, aby zastosować filtrowanie testów. Użyj też adnotacji JavaRequiresFlagsEnabled
iRequiredFlagsDisabled
, aby określić wymagania dotyczące flagi w przypadku testu.Test po stronie urządzenia korzysta z klasy
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() {} }
Test po stronie hosta korzysta z klasy
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() {} }
Dodaj
jflag-unit
i biblioteki wygenerowane przez aconfig do sekcjistatic_libs
w pliku kompilacji testu:android_test { name: "FlagAnnotationTests", srcs: ["*.java"], static_libs: [ "androidx.test.rules", "my_aconfig_lib", "flag-junit", "platform-test-annotations", ], test_suites: ["general-tests"], }
Aby uruchomić test lokalnie, użyj tego polecenia:
atest FlagAnnotationTests
Jeśli flaga
Flags.FLAG_FLAG_NAME_1
jest wyłączona, wynik testu to:[1/2] com.cts.flags.FlagAnnotationTest#test1: ASSUMPTION_FAILED (10ms) [2/2] com.cts.flags.FlagAnnotationTest#test2: PASSED (2ms)
W przeciwnym razie wynik testu to:
[1/2] com.cts.flags.FlagAnnotationTest#test1: PASSED (2ms) [2/2] com.cts.flags.FlagAnnotationTest#test2: ASSUMPTION_FAILED (10ms)
Wartości domyślne urządzenia
Zainicjowany obiekt SetFlagsRule
używa wartości flag z urządzenia. Jeśli wartość flagi na urządzeniu nie zostanie zastąpiona, np. za pomocą adb, wartość domyślna będzie taka sama jak konfiguracja wersji kompilacji. Jeśli wartość na urządzeniu została zastąpiona, SetFlagsRule
używa wartości zastąpienia jako domyślnej.
Jeśli ten sam test jest wykonywany w różnych konfiguracjach wersji, wartość flag, które nie zostały jawnie ustawione za pomocą SetFlagsRule
, może się różnić.
Po każdym teście SetFlagsRule
przywraca instancję FeatureFlags
w Flags
do pierwotnego FeatureFlagsImpl
, aby nie wywoływać efektów ubocznych w przypadku innych metod i klas testowych.