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

Mit der Einführung von Flags zur Einführung von Funktionen gibt es neue Testrichtlinien, die Sie einhalten müssen:

  • Ihre Tests müssen sowohl das aktivierte als auch das deaktivierte Verhalten des Flags abdecken.
  • Sie müssen die offiziellen Mechanismen verwenden, um während des Tests Flag-Werte festzulegen.
  • In xTS-Tests sollten keine Flag-Werte überschrieben werden.

Der nächste Abschnitt enthält die offiziellen Mechanismen, mit denen Sie diese Richtlinien einhalten müssen.

Code mit Markierungen testen

Testszenario Verwendeter Mechanismus
Lokale Tests, wenn sich die Flag-Werte häufig ändern Android-Debug-Bridge wie unter Wert eines Flags zur Laufzeit ändern beschrieben
Lokale Tests, wenn sich die Flag-Werte selten ändern Datei mit Flag-Werten, wie unter Werte für das Flags der Funktionseinführung festlegen beschrieben
End-to-End-Tests, bei denen sich Flag-Werte ändern FeatureFlagTargetPreparer, wie unter End-to-End-Tests erstellen beschrieben
Unittests, bei denen sich Flag-Werte ändern SetFlagsRule mit @EnableFlags und @DisableFlags, wie unter Einheitentests erstellen (Java und Kotlin) oder Einheitentests erstellen (C und C++) beschrieben
End-to-End- oder Einheitentests, bei denen Flag-Werte nicht geändert werden können CheckFlagsRule, wie unter End-to-End- oder Einheitentests 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 in die AndroidTest.xml-Testmodulkonfigurationsdatei FeatureFlagTargetPreparer- und Flag-Wertüberschreibungen ein:

  <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üge 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. Dabei ist name 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 Einheitentests 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. Verwende 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 SetFlagsRule eingebunden wird und mehrere Flags gleichzeitig aktiviert werden:

  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, die zum Überschreiben von Flag-Werten bereitgestellt wird. Informationen dazu, wie SetFlagsRule Standardwerte ermittelt, finden Sie unter Gerätestandardwerte.
  • @EnableFlags ist eine Annotation, die eine beliebige Anzahl von Flag-Namen akzeptiert. Verwenden Sie zum Deaktivieren von Flags @DisableFlags. Sie können diese Annotationen entweder auf eine Methode oder auf eine Klasse anwenden.

Lege die Flag-Werte für den gesamten Testprozess fest, beginnend mit SetFlagsRule, das vor jeglichen @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.

Achten Sie darauf, dass Flags richtig festgelegt sind

Wie bereits erwähnt, wird SetFlagsRule mit der JUnit @Rule-Annotation verwendet. Das bedeutet, dass SetFlagsRule nicht sicherstellen kann, dass Ihre Flags während des Konstruktors der Testklasse oder von mit @BeforeClass oder @AfterClass annotierten Methoden korrekt festgelegt werden.

Verwenden Sie die SetFlagsRule.ClassRule-Methode, damit Test-Fixierungen mit dem richtigen Klassenwert erstellt werden, damit sie erst mit einer @Before-annotierten Einrichtungsmethode erstellt werden:

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

Durch Hinzufügen der Klassenregel SetFlagsRule.ClassRule schlägt test_flag_foo_turned_on vor der Ausführung fehl, 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 die Parametrisierung auch verwenden, um Tests für mehrere Flag-Konfigurationen auszufü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. Zum Beispiel wird legacyBarLogic nur ausgeführt, wenn FLAG_BAR deaktiviert ist. Dies geschieht bei zwei der vier Flag-Kombinationen. Bei den anderen beiden Kombinationen wird legacyBarLogic übersprungen.

Es gibt zwei Methoden, um die Parametrisierungen für Ihre Flags zu erstellen:

  • FlagsParameterization.allCombinationsOf(String...) führt 2^n Durchläufe 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: Ein Flag führt 2x Tests und vier Flags 5x Flags aus.

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 der Makros TEST und TEST_F werden die Makros TEST_WITH_FLAGS und TEST_F_WITH_FLAGS 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.
    • REQUIRES_FLAGS_DISABLED definiert eine Reihe von Feature-Flags, die die deaktivierte Bedingung 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 Flags verwendet wird, die in einer Konfigurationsdatei definiert sind. 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 der Build-Datei Android.bp die von aconfig generierten Bibliotheken und 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, lautet 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, lautet das Testergebnis:

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

End-to-End- oder Einheitentests erstellen, bei denen sich die Flag-Werte nicht ändern

Verwenden Sie für Testfälle, in denen Sie Flags nicht überschreiben und Tests nur filtern können, wenn sie auf dem aktuellen Flag-Status basieren, die Regel CheckFlagsRule mit den Annotationen RequiresFlagsEnabled und RequiresFlagsDisabled.

Die folgenden Schritte zeigen, wie Sie einen End-to-End- oder Einheitentest 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-Annotationen 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 dem Abschnitt static_libs der Build-Datei für Ihren Test jflag-unit und eine von einer Konfiguration 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)
    

Standardwerte des Geräts

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

Wenn derselbe Test unter verschiedenen Releasekonfigurationen ausgeführt wird, kann der Wert von Flags, die nicht explizit mit SetFlagsRule festgelegt sind, variieren.

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