Testowanie kodu za pomocą flag uruchamiania funkcji

Wprowadziliśmy flagi uruchamiania funkcji, więc musisz przestrzegać nowych zasad testowania:

  • 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 sekcji 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 – jak opisano w artykule Tworzenie testów kompleksowych.
Testy jednostkowe, w których zmieniają się wartości flagi SetFlagsRule@EnableFlags@DisableFlags zgodnie z opisem w artykule Tworzenie testów jednostkowych (Java i Kotlin) lub Tworzenie testów jednostkowych (C i C++).
Testy kompleksowe lub testowanie 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 testowanie na urządzeniu od początku do końca. Ta klasa przyjmuje jako dane wejściowe zastąpienia wartości flagi, 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.

Stosowanie FeatureFlagTargetPreparer w konfiguracji modułu testowego

Aby zastosować FeatureFlagTargetPreparer w konfiguracji modułu testowego, uwzględnij FeatureFlagTargetPreparer i zastąpienia wartości flagi w AndroidTest.xmlpliku konfiguracji modułu testowego:

  <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órym name jest zawsze ustawione na flag-value, a value na namespace/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:

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

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Określ opcje wartości flagi w sekcji test_module_config pliku kompilacji Android.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 flag, przy czym name jest zawsze ustawione na flag-value, a value na namespace/aconfigPackage.flagName=true|false.

Tworzenie testów jednostkowych (Java i Kotlin)

W tej sekcji opisujemy podejście do zastępowania 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:

  1. Użyj klasy SetFlagsRule z adnotacjami @EnableFlags@DisableFlags, aby przetestować wszystkie gałęzie kodu.
  2. Używaj metody SetFlagsRule.ClassRule, aby uniknąć typowych błędów w testach.
  3. 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 klasy SetFlagsRule.
  • SetFlagsRule to klasa pomocnicza, która umożliwia zastępowanie wartości flag. Informacje o tym, jak SetFlagsRule 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. Wyłączając flagi, użyj @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 wykonaniu wszystkich metod 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, dzięki czemu elementy stałe nie będą 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 test_flag_foo_turned_on kończy się niepowodzeniem przed uruchomieniem, gdy FLAG_FLAG_FOO jest odczytywana przez konstruktor DemoClass.

Jeśli flaga ma być włączona dla wszystkich zajęć, przenieś adnotację @EnableFlags na poziom zajęć (przed deklaracją zajęć). Przeniesienie adnotacji na poziom klasy pozwala SetFlagsRule.ClassRule zapewnić prawidłowe ustawienie flagi w konstruktorze klasy testowej lub w dowolnych metodach z adnotacjami @BeforeClass lub @AfterClass.

Przeprowadzanie testów w różnych konfiguracjach flag

Wartości flag można ustawiać dla każdego testu z osobna, dlatego 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 bez parametryzacji ta klasa przeprowadza 3 testy (fooLogic, legacyBarLogicnewBarLogic). Metoda fooLogic jest wykonywana z wartościami FLAG_FOOFLAG_BAR ustawionymi na urządzeniu.

Po dodaniu parametryzacji metoda FlagsParameterization.allCombinationsOf tworzy 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 ma wartość false.

Zamiast bezpośrednio zmieniać wartości flag, adnotacje @DisableFlags@EnableFlags modyfikują je 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 uruchamiają 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.

  1. W źródle testowym umieść definicje makr i biblioteki wygenerowane przez aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. W źródle testowym zamiast makr TESTTESTF w przypadku przypadków testowych użyj makr 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 makr TESTTEST_F używane są makra TEST_WITH_FLAGSTEST_F_WITH_FLAGS.
    • REQUIRES_FLAGS_ENABLED definiuje zestaw flag wersji funkcji, które muszą spełniać warunek włączenia. Możesz wpisać te flagi w makrach ACONFIG_FLAG lub LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED definiuje zestaw flag funkcji, które muszą spełniać warunek wyłączenia. Możesz wpisać te flagi w makrach ACONFIG_FLAG lub LEGACY_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.
  3. W pliku 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"],
      ...
    }
    
  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 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 RequiresFlagsEnabledRequiresFlagsDisabled.

Poniższe kroki pokazują, jak utworzyć i uruchomić test kompleksowy lub jednostkowy, w którym nie można zastąpić wartości flag:

  1. W kodzie testowym użyj znaku CheckFlagsRule, aby zastosować filtrowanie testów. Użyj też adnotacji Java RequiresFlagsEnabledRequiredFlagsDisabled, 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() {}
    }
    
  2. Dodaj jflag-unit i biblioteki wygenerowane przez aconfig do sekcji static_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"],
    }
    
  3. 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 wartości domyślnej.

Jeśli ten sam test jest wykonywany w ramach różnych konfiguracji 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ę FeatureFlagsFlags do pierwotnego FeatureFlagsImpl, aby nie wywoływać efektów ubocznych w przypadku innych metod i klas testowych.