Testowanie kodu za pomocą flag uruchamiania funkcji

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@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 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ó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 flagi, 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, 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:

  1. Użyj klasy SetFlagsRule z adnotacjami @EnableFlags@DisableFlags, aby przetestować wszystkie gałęzie kodu.
  2. Aby uniknąć typowych błędów w testach, używaj metody SetFlagsRule.ClassRule.
  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. 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, legacyBarLogicnewBarLogic). Metoda fooLogic jest uruchamiana 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ą 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.

  1. W źródle testowym uwzględnij definicje makr i biblioteki wygenerowane przez aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. W źródle testowym zamiast makr TEST i TESTF w przypadkach testowych użyj makr TEST_WITH_FLAGS i TEST_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 udostępniania 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 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"],
      ...
    }
    
  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żej znajdziesz instrukcje tworzenia i uruchamiania testu kompleksowego lub jednostkowego, w którym nie można zastąpić wartości flag:

  1. W kodzie testowym użyj 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 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ę FeatureFlagsFlags do pierwotnego FeatureFlagsImpl, aby nie wywoływać efektów ubocznych w przypadku innych metod i klas testowych.