Code innerhalb von Flags für die Einführung von Funktionen testen

Mit der Einführung von Flags für die Einführung von Funktionen gelten neue Testanforderungen, die Sie einhalten müssen:

  • Ihre Tests müssen sowohl das Verhalten bei aktivierter als auch bei deaktivierter Markierung abdecken.
  • Sie müssen die offiziellen Mechanismen verwenden, um während der Tests Flag-Werte festzulegen.
  • In xTS-Tests sollten keine Flag-Werte überschrieben werden.

Im nächsten Abschnitt werden die offiziellen Mechanismen beschrieben, die Sie zur Einhaltung dieser Richtlinien verwenden müssen.

Code mit Markierungen testen

Testszenario Verwendeter Mechanismus
Lokale Tests, wenn sich Flaggenwerte häufig ändern Android Debug Bridge wie unter Wert eines Flags zur Laufzeit ändern beschrieben
Lokale Tests, wenn sich Flag-Werte nicht oft ändern Flag-Wertdatei wie unter Werte für das Flag zur Produktveröffentlichung festlegen beschrieben
End-to-End-Tests, bei denen sich Flag-Werte ändern FeatureFlagTargetPreparer wie im Abschnitt End-to-End-Tests erstellen beschrieben
Unittests, bei denen sich Flag-Werte ändern SetFlagsRule mit @EnableFlags und @DisableFlags, wie unter Einheitstests erstellen (Java und Kotlin) oder Einheitstests erstellen (C und C++) beschrieben
Ende-zu-Ende- oder Unit-Tests, bei denen sich Flag-Werte nicht ändern können CheckFlagsRule wie unter Ende-zu-Ende- oder Unit-Tests erstellen, bei denen sich Flag-Werte nicht ändern beschrieben

End-to-End-Tests erstellen

AOSP bietet eine Klasse namens FeatureFlagTargetPreparer, die End-to-End-Tests auf einem Gerät ermöglicht. Diese Klasse akzeptiert Überschreibungen von Flag-Werten als Eingabe, setzt diese Flags vor der Testausführung in der Gerätekonfiguration und stellt sie nach der Ausführung wieder her.

Sie können die Funktionen der Klasse FeatureFlagTargetPreparer auf Testmodul- und Testkonfigurationsebene anwenden.

FeatureFlagTargetPreparer in einer Testmodulkonfiguration anwenden

Wenn Sie FeatureFlagTargetPreparer in einer Testmodulkonfiguration anwenden möchten, fügen Sie der Konfigurationsdatei des Testmoduls AndroidTest.xml Überschreibungen für FeatureFlagTargetPreparer und Flag-Werte hinzu:

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

Dabei gilt:

  • target.preparer class ist immer auf com.android.tradefed.targetprep.FeatureFlagTargetPreparer festgelegt.
  • option ist die Flag-Überschreibung, wobei name immer auf flag-value und value auf namespace/aconfigPackage.flagName=true|false festgelegt ist.

Parameterisierte Testmodule basierend auf Flag-Status erstellen

So erstellen Sie parametrisierte Testmodule basierend auf Flag-Status:

  1. Fügen Sie FeatureFlagTargetPreparer in die Konfigurationsdatei des AndroidTest.xml-Testmoduls ein:

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Geben Sie die Optionen für den Flag-Wert im Abschnitt test_module_config einer Android.bp-Builddatei an:

    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"},
        ],
    }
    

    Das Feld options enthält die Flag-Überschreibungen. name ist immer auf flag-value und value auf namespace/aconfigPackage.flagName=true|false gesetzt.

Einheitentests erstellen (Java und Kotlin)

In diesem Abschnitt wird beschrieben, wie Sie AConfig-Flag-Werte in Java- und Kotlin-Tests auf Klassen- und Methodenebene (pro Test) überschreiben.

So schreiben Sie automatisierte Unit-Tests in einer großen Codebasis mit einer großen Anzahl von Flags:

  1. Verwenden Sie die Klasse SetFlagsRule mit den Anmerkungen @EnableFlags und @DisableFlags, um alle Codezweige zu testen.
  2. Verwenden Sie die Methode SetFlagsRule.ClassRule, um häufige Testfehler zu vermeiden.
  3. Mit FlagsParameterization können Sie Ihre Klassen mit einer Vielzahl von Flag-Konfigurationen testen.

Alle Codezweige testen

Bei Projekten, in denen die statische Klasse zum Zugriff auf Flags verwendet wird, wird die Hilfsklasse SetFlagsRule bereitgestellt, um Flag-Werte zu überschreiben. Das folgende Code-Snippet zeigt, wie Sie SetFlagsRule einfügen und mehrere Flags gleichzeitig aktivieren:

  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() {
    ...
    }

Dabei gilt:

  • @Rule ist eine Annotation, mit der die Flag-JUnit-Abhängigkeit der Klasse SetFlagsRule hinzugefügt wird.
  • SetFlagsRule ist eine Hilfsklasse, mit der Flag-Werte überschrieben werden können. Informationen dazu, wie SetFlagsRule Standardwerte ermittelt, finden Sie unter Gerätestandardwerte.
  • @EnableFlags ist eine Anmerkung, die eine beliebige Anzahl von Flaggennamen akzeptiert. Verwenden Sie @DisableFlags, um Flags zu deaktivieren. Sie können diese Anmerkungen entweder auf eine Methode oder eine Klasse anwenden.

Legen Sie Flag-Werte für den gesamten Testprozess fest, beginnend mit SetFlagsRule, was vor allen mit @Before annotierten Einrichtungsmethoden im Test liegt. Die Flag-Werte werden wieder auf ihren vorherigen Zustand zurückgesetzt, wenn SetFlagsRule abgeschlossen ist. Das ist nach allen mit @After annotierten Einrichtungsmethoden der Fall.

Prüfen, ob die Flags richtig gesetzt sind

Wie bereits erwähnt, wird SetFlagsRule mit der JUnit-@Rule-Anmerkung verwendet. Das bedeutet, dass SetFlagsRule nicht dafür sorgen kann, dass Ihre Flags während des Konstruktors der Testklasse oder in Methoden mit der Anmerkung @BeforeClass oder @AfterClass richtig festgelegt werden.

Damit Test-Fixtures mit dem richtigen Klassenwert erstellt werden, verwenden Sie die Methode SetFlagsRule.ClassRule, damit Ihre Fixtures erst erstellt werden, wenn eine mit @Before annotierte Einrichtungsmethode aufgerufen wird:

  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() {
      ...
    }
  }

Wenn Sie die SetFlagsRule.ClassRule-Klassenregel hinzufügen, schlägt test_flag_foo_turned_on fehl, bevor es ausgeführt wird, wenn FLAG_FLAG_FOO vom Konstruktor von DemoClass gelesen wird.

Wenn für den gesamten Kurs ein Flag aktiviert werden muss, verschieben Sie die @EnableFlags-Anmerkung auf Kursebene (vor die Kursdeklaration). Durch das Verschieben der Anmerkung auf Klassenebene kann SetFlagsRule.ClassRule dafür sorgen, dass das Flag im Konstruktor der Testklasse oder in allen mit @BeforeClass oder @AfterClass annotierten Methoden korrekt festgelegt wird.

Tests für mehrere Flag-Konfigurationen ausführen

Da Sie Flag-Werte pro Test festlegen können, können Sie mithilfe der Parameterisierung auch Tests für mehrere Flag-Konfigurationen ausführen:

...
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() {...}
}

Hinweis: Mit SetFlagsRule, aber ohne Parameter werden in dieser Klasse drei Tests ausgeführt (fooLogic, legacyBarLogic und newBarLogic). Die Methode fooLogic wird mit den Werten von FLAG_FOO und FLAG_BAR ausgeführt, die auf dem Gerät festgelegt sind.

Wenn eine Parameterisierung hinzugefügt wird, werden mit der FlagsParameterization.allCombinationsOf-Methode alle möglichen Kombinationen der Flags FLAG_FOO und FLAG_BAR erstellt:

  • FLAG_FOO ist true und FLAG_BAR ist true
  • FLAG_FOO ist true und FLAG_BAR ist false
  • FLAG_FOO ist false und FLAG_BAR ist true
  • FLAG_FOO ist falsch und FLAG_BAR ist false

Anstatt Flag-Werte direkt zu ändern, ändern Anmerkungen vom Typ @DisableFlags und @EnableFlags Flag-Werte basierend auf Parameterbedingungen. Beispiel: legacyBarLogic wird nur ausgeführt, wenn FLAG_BAR deaktiviert ist. Das ist bei zwei der vier Flag-Kombinationen der Fall. Bei den anderen beiden Kombinationen wird legacyBarLogic übersprungen.

Es gibt zwei Methoden zum Erstellen der Parameterisierungen für Ihre Flags:

  • FlagsParameterization.allCombinationsOf(String...) führt 2 hoch n Ausführungen jedes Tests aus. Mit einer Flagge werden beispielsweise zwei Tests ausgeführt, mit vier Flags 16 Tests.

  • FlagsParameterization.progressionOf(String...) führt n + 1 Ausführungen jedes Tests aus. Beispiel: Mit einem Flag werden doppelt so viele Tests ausgeführt wie mit vier Flags.

Unittests erstellen (C und C++)

AOSP enthält Flag-Wert-Makros für C- und C++-Tests, die im GoogleTest-Framework geschrieben wurden.

.
  1. Fügen Sie in Ihrer Testquelle die Makrodefinitionen und die von aconfig generierten Bibliotheken ein:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. Verwenden Sie in Ihrer Testquelle anstelle der Makros TEST und TESTF für Ihre Testfälle TEST_WITH_FLAGS und 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");
    }
    

    Dabei gilt:

    • Anstelle von TEST- und TEST_F-Makros werden TEST_WITH_FLAGS- und TEST_F_WITH_FLAGS-Makros verwendet.
    • REQUIRES_FLAGS_ENABLED definiert eine Reihe von Flags für die Funktion, die die Aktivierungsbedingung erfüllen müssen. Sie können diese Flags in ACONFIG_FLAG- oder LEGACY_FLAG-Makros schreiben.
    • Mit REQUIRES_FLAGS_DISABLED werden eine Reihe von Feature-Flags definiert, die die Bedingung „deaktiviert“ erfüllen müssen. Sie können diese Flags in ACONFIG_FLAG- oder LEGACY_FLAG-Makros schreiben.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) ist ein Makro, das für in Konfigurationsdateien definierte Flags verwendet wird. Dieses Makro akzeptiert einen Namespace (TEST_NS) und einen Flag-Namen (readwrite_enabled_flag).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) ist ein Makro, das für Flags verwendet wird, die standardmäßig in der Gerätekonfiguration festgelegt sind.
  3. Fügen Sie in Ihrer Android.bp-Builddatei die von aconfig generierten Bibliotheken und die relevanten Makrobibliotheken als Testabhängigkeit hinzu:

    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. Führen Sie die Tests mit dem folgenden Befehl lokal aus:

    atest FlagMacrosTests
    

    Wenn das Flag my_namespace.android.myflag.tests.my_flag deaktiviert ist, ist das Testergebnis:

    [1/2] MyTest#test1: IGNORED (0ms)
    [2/2] MyTestF#test2: PASSED (0ms)
    

    Wenn das Flag my_namespace.android.myflag.tests.my_flag aktiviert ist, ist das Testergebnis:

    [1/2] MyTest#test1: PASSED (0ms)
    [2/2] MyTestF#test2: IGNORED (0ms)
    

End-to-End- oder Unit-Tests erstellen, bei denen sich Flag-Werte nicht ändern

Bei Testfällen, bei denen Sie Flags nicht überschreiben und Tests nur filtern können, wenn sie auf dem aktuellen Flag-Status basieren, verwenden Sie die Regel CheckFlagsRule mit den Anmerkungen RequiresFlagsEnabled und RequiresFlagsDisabled.

In den folgenden Schritten wird beschrieben, wie Sie einen End-to-End- oder Unit-Test erstellen und ausführen, bei dem Flag-Werte nicht überschrieben werden können:

  1. Verwenden Sie in Ihrem Testcode CheckFlagsRule, um die Testfilterung anzuwenden. Verwenden Sie außerdem die Java-Anmerkungen RequiresFlagsEnabled und RequiredFlagsDisabled, um die Flag-Anforderungen für Ihren Test anzugeben.

    Für den geräteseitigen Test wird die Klasse DeviceFlagsValueProvider verwendet:

    @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() {}
    }
    

    Für den hostseitigen Test wird die Klasse HostFlagsValueProvider verwendet:

    @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. Fügen Sie der Build-Datei für Ihren Test im Abschnitt static_libs jflag-unit- und aconfig-generierte Bibliotheken hinzu:

    android_test {
        name: "FlagAnnotationTests",
        srcs: ["*.java"],
        static_libs: [
            "androidx.test.rules",
            "my_aconfig_lib",
            "flag-junit",
            "platform-test-annotations",
        ],
        test_suites: ["general-tests"],
    }
    
  3. Verwenden Sie den folgenden Befehl, um den Test lokal auszuführen:

    atest FlagAnnotationTests
    

    Wenn das Flag Flags.FLAG_FLAG_NAME_1 deaktiviert ist, ist das Testergebnis:

    [1/2] com.cts.flags.FlagAnnotationTest#test1: ASSUMPTION_FAILED (10ms)
    [2/2] com.cts.flags.FlagAnnotationTest#test2: PASSED (2ms)
    

    Andernfalls ist das Testergebnis:

    [1/2] com.cts.flags.FlagAnnotationTest#test1: PASSED (2ms)
    [2/2] com.cts.flags.FlagAnnotationTest#test2: ASSUMPTION_FAILED (10ms)
    

Gerätestandardwerte

Die initialisierte SetFlagsRule verwendet Flag-Werte vom Gerät. Wenn der Flag-Wert auf dem Gerät nicht überschrieben wird, z. B. mit adb, entspricht der Standardwert der Release-Konfiguration des Builds. Wenn der Wert auf dem Gerät überschrieben wurde, verwendet SetFlagsRule den Überschreibungswert als Standard.

Wenn derselbe Test unter verschiedenen Release-Konfigurationen ausgeführt wird, kann der Wert von Flags, die nicht explizit mit SetFlagsRule festgelegt wurden, variieren.

Nach jedem Test stellt SetFlagsRule die FeatureFlags-Instanz in Flags wieder auf ihren ursprünglichen FeatureFlagsImpl zurück, damit es keine Nebenwirkungen auf andere Testmethoden und ‑klassen hat.