Wraz z wprowadzeniem flag wdrażania funkcji obowiązują nowe zasady testowania:
- Testy muszą obejmować zarówno włączone, jak i wyłączone działanie 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 testowy | Używany mechanizm |
---|---|
testowanie lokalne, gdy wartości flag często się zmieniają; | most debugowania Androida, jak opisano w artykule Zmienianie wartości flagi w czasie wykonywania. |
testowanie lokalne, gdy wartości flag nie zmieniają się często; | Plik wartości flagi, jak opisano w sekcji Ustawianie wartości flagi uruchomienia funkcji |
kompleksowe testowanie, w którym zmieniają się wartości flagi; | FeatureFlagTargetPreparer zgodnie z instrukcjami w artykule Tworzenie kompleksowych testów |
Testowanie jednostkowe, w którym zmieniają się wartości flag | 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 jak opisano w Tworzenie testów kompleksowych lub jednostkowych, w których wartości flag nie ulegają zmianie |
Tworzenie kompleksowych testów
AOSP udostępnia klasę o nazwie FeatureFlagTargetPreparer
, która umożliwia kompleksowe testowanie urządzenia. Ta klasa przyjmuje zastąpienia wartości flagi jako dane wejściowe, ustawia te flagi w konfiguracji urządzeń przed wykonaniem testu i przywraca flagi po jego wykonaniu.
Funkcje klasy FeatureFlagTargetPreparer
możesz stosować na poziomie modułu testu i konfiguracji testu.
Stosowanie FeatureFlagTargetPreparer w konfiguracji modułu testowego
Aby zastosować FeatureFlagTargetPreparer
w konfiguracji modułu testowego, dodaj zastąpienia wartości FeatureFlagTargetPreparer
i flagi w pliku konfiguracji modułu testowego 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>
Gdzie:
target.preparer class
ma zawsze wartośćcom.android.tradefed.targetprep.FeatureFlagTargetPreparer
.option
to flaga zastępcza, w którejname
ma zawsze wartośćflag-value
, avalue
– wartośćnamespace/aconfigPackage.flagName=true|false
.
Tworzenie modułów testów parametrycznych na podstawie stanów flagi
Aby utworzyć moduły testów parametrycznych na podstawie stanów flagi:
Dołącz plik
FeatureFlagTargetPreparer
do pliku konfiguracjiAndroidTest.xml
modułu testowego:<target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
W sekcji
test_module_config
pliku kompilacjiAndroid.bp
określ opcje wartości flagi: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, w którychname
jest zawsze ustawiony naflag-value
, avalue
nanamespace/aconfigPackage.flagName=true|false
.
Tworzenie testów jednostkowych (Java i Kotlin)
W tej sekcji opisano podejście do zastępowania wartości flagi aconfig na poziomie klasy i metody (na podstawie testu) w testach w Javie i Kotlinie.
Aby napisać automatyczne testy jednostkowe w dużej bazie kodu z dużą liczbą flag:
- Aby przetestować wszystkie gałęzie kodu, użyj klasy
SetFlagsRule
z adnotacjami@EnableFlags
i@DisableFlags
. - Aby uniknąć typowych błędów testowania, używaj metody
SetFlagsRule.ClassRule
. - Użyj opcji
FlagsParameterization
, aby przetestować swoje klasy w szerokim zakresie konfiguracji flagi.
Testowanie wszystkich gałęzi kodu
W przypadku projektów, które używają klasy statycznej do uzyskiwania dostępu do flag, dostępna jest klasa pomocnicza SetFlagsRule
, która umożliwia zastąpienie wartości flag. Ten 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 służąca do dodania zależności flagi JUnit klasySetFlagsRule
.SetFlagsRule
to klasa pomocnicza, która służy do zastępowania wartości flag. Informacje o tym, jakSetFlagsRule
określa domyślne wartości, znajdziesz w artykule Domyślne wartości urządzenia.@EnableFlags
to adnotacja, która akceptuje dowolną liczbę nazw flag. Aby wyłączyć flagi, użyj parametru@DisableFlags
. Te adnotacje możesz zastosować do metody lub klasy.
Ustaw wartości flagi dla całego procesu testowania, zaczynając od SetFlagsRule
, która jest wcześniejsza niż jakakolwiek metoda konfiguracji z adnotacją @Before
w teście. Wartości flagi wracają do poprzedniego stanu po zakończeniu konfiguracji SetFlagsRule
, czyli po zastosowaniu wszystkich metod konfiguracji oznaczonych adnotacją @After
.
Upewnij się, że flagi są prawidłowo ustawione
Jak już wspomnieliśmy, SetFlagsRule
jest używane z adnotacją JUnit @Rule
, co oznacza, że SetFlagsRule
nie może zagwarantować, że flagi są prawidłowo ustawione podczas konstruktora testowej klasy lub w dowolnej metodzie z adnotacjami @BeforeClass
lub @AfterClass
.
Aby mieć pewność, że testowe stałe są tworzone z właściwą wartością klasy, użyj metody SetFlagsRule.ClassRule
, aby stałe nie były tworzone, dopóki nie zostanie użyta metoda 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
zawodzi przed uruchomieniem, gdy konstruktor funkcji DemoClass
odczytuje zmienną FLAG_FLAG_FOO
.
Jeśli flaga musi być włączona na poziomie całego zajęcia, przełóż adnotację @EnableFlags
do poziomu zajęć (przed deklaracją zajęć). Przeniesienie adnotacji na poziom klasy pozwala SetFlagsRule.ClassRule
upewnić się, że flaga jest prawidłowo ustawiona podczas konstruktora klasy testowej lub w metodach z adnotacjami @BeforeClass
lub @AfterClass
.
Uruchamianie testów w różnych konfiguracjach flag
Ponieważ wartości flag możesz ustawiać osobno dla każdego testu, możesz też użyć parametryzacji, aby przeprowadzać testy z różnymi konfiguracjami 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 przy użyciu SetFlagsRule
, ale bez parametryzacji, ta klasa uruchamia 3 testy (fooLogic
, legacyBarLogic
i newBarLogic
). Metoda fooLogic
działa z dowolnymi 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
– wartośćfalse
.
Zamiast bezpośredniej zmiany wartości flagi adnotacje @DisableFlags
i @EnableFlags
zmieniają wartości flagi 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 2 x, a 4 flagi – testy 16 x.FlagsParameterization.progressionOf(String...)
wykonuje n + 1 uruchomień każdego testu. Na przykład jedna flaga uruchamia 2 testy, a 4 flagi uruchamiają 5 testów.
Tworzenie testów jednostkowych (C i C++)
AOSP zawiera makra wartości flagi dla testów C i C++, które zostały napisane w ramach frameworku GoogleTest.
.W źródle testu uwzględnij definicje makr i biblioteki utworzone za pomocą narzędzia aconfig:
#include <flag_macros.h> #include "android_cts_flags.h"
W źródle testowym zamiast makro
TEST
iTESTF
w przypadku testów użyj makroTEST_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 makro
TEST
iTEST_F
używane są makroTEST_WITH_FLAGS
iTEST_F_WITH_FLAGS
. REQUIRES_FLAGS_ENABLED
definiuje zestaw flag funkcji, które muszą spełniać warunek włączenia. Możesz je zapisać w makrachACONFIG_FLAG
lubLEGACY_FLAG
.REQUIRES_FLAGS_DISABLED
definiuje zestaw flag funkcji, które muszą spełniać warunek wyłączony. Możesz je zapisać w makrachACONFIG_FLAG
lubLEGACY_FLAG
.ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag)
to makro używane do flag zdefiniowanych w plikach aconfig. To makro akceptuje nazwę przestrzeni nazw (TEST_NS
) i nazwę flagi (readwrite_enabled_flag
).LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag)
to makro używane do flag ustawionych domyślnie w konfiguracji urządzenia.
- Zamiast makro
W pliku kompilacji
Android.bp
dodaj biblioteki wygenerowane przez aconfig i odpowiednie biblioteki makr jako testowe zależności: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 jest taki:[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:[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 przypadkach testów, w których nie można zastąpić flag i testów, które można filtrować tylko wtedy, gdy są oparte na bieżącym stanie flagi, użyj reguły CheckFlagsRule
z annotacjami RequiresFlagsEnabled
i RequiresFlagsDisabled
.
Z tych instrukcji dowiesz się, jak utworzyć i uruchomić test całościowy lub jednostkowy, w którym wartości flag nie można zastąpić:
W kodzie testowym użyj funkcji
CheckFlagsRule
, aby zastosować testowe filtrowanie. Użyj też adnotacji JavaRequiresFlagsEnabled
iRequiredFlagsDisabled
, aby określić wymagania dotyczące flagi w teście.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 biblioteki
jflag-unit
i biblioteki wygenerowane przez aconfig do sekcjistatic_libs
pliku kompilacji przeznaczonego do 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 jest następujący:[1/2] com.cts.flags.FlagAnnotationTest#test1: ASSUMPTION_FAILED (10ms) [2/2] com.cts.flags.FlagAnnotationTest#test2: PASSED (2ms)
W przeciwnym razie wynik testu jest następujący:
[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
Inicjalizowany SetFlagsRule
używa wartości flagi z urządzenia. Jeśli wartość flagi na urządzeniu nie została zastąpiona, np. za pomocą adb, domyślna wartość jest 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 ramach różnych konfiguracji wersji, wartość flag nie ustawionych wyraźnie za pomocą SetFlagsRule
może się różnić.
Po każdym teście SetFlagsRule
przywraca instancję FeatureFlags
w Flags
do jej pierwotnego stanu FeatureFlagsImpl
, aby nie miała ona efektów ubocznych na inne metody i klasy testów.