Testuj kod w ramach flag uruchamiania funkcji

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@EnableFlags@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órej name ma zawsze wartość flag-value, a value – 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:

  1. Dołącz plik FeatureFlagTargetPreparer do pliku konfiguracji AndroidTest.xml modułu testowego:

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. W sekcji test_module_config pliku kompilacji Android.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órych name jest zawsze ustawiony na flag-value, a value na namespace/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:

  1. Aby przetestować wszystkie gałęzie kodu, użyj klasy SetFlagsRule z adnotacjami @EnableFlags i @DisableFlags.
  2. Aby uniknąć typowych błędów testowania, używaj metody SetFlagsRule.ClassRule.
  3. 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 klasy SetFlagsRule.
  • SetFlagsRule to klasa pomocnicza, która służy do zastępowania wartości flag. Informacje o tym, jak SetFlagsRule 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.ClassRuleupewnić 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, legacyBarLogicnewBarLogic). Metoda fooLogic działa z dowolnymi wartościami FLAG_FOOFLAG_BAR ustawionymi na urządzeniu.

Po dodaniu parametryzacji metoda FlagsParameterization.allCombinationsOftworzy wszystkie możliwe kombinacje flag FLAG_FOOFLAG_BAR:

  • FLAG_FOO to true, a FLAG_BAR to true
  • FLAG_FOO to true, a FLAG_BAR to false
  • FLAG_FOO to false, a FLAG_BAR to true
  • FLAG_FOO ma wartość fałsz, a FLAG_BAR – wartość false.

Zamiast bezpośredniej zmiany wartości flagi adnotacje @DisableFlags@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.

.
  1. 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"
    
  2. W źródle testowym zamiast makro TESTTESTF w przypadku testów użyj makro TEST_WITH_FLAGSTEST_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 TESTTEST_F używane są makro TEST_WITH_FLAGSTEST_F_WITH_FLAGS.
    • REQUIRES_FLAGS_ENABLED definiuje zestaw flag funkcji, które muszą spełniać warunek włączenia. Możesz je zapisać w makrach ACONFIG_FLAG lub LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED definiuje zestaw flag funkcji, które muszą spełniać warunek wyłączony. Możesz je zapisać w makrach ACONFIG_FLAG lub LEGACY_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.
  3. 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"],
      ...
    }
    
  4. 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 RequiresFlagsEnabledRequiresFlagsDisabled.

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ć:

  1. W kodzie testowym użyj funkcji CheckFlagsRule, aby zastosować testowe filtrowanie. Użyj też adnotacji Java RequiresFlagsEnabledRequiredFlagsDisabled, 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() {}
    }
    
  2. Dodaj biblioteki jflag-unit i biblioteki wygenerowane przez aconfig do sekcji static_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"],
    }
    
  3. 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ę FeatureFlagsFlags do jej pierwotnego stanu FeatureFlagsImpl, aby nie miała ona efektów ubocznych na inne metody i klasy testów.