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 gibt es neue Testrichtlinien, die Sie einhalten müssen:

  • Ihre Tests müssen sowohl das Verhalten des Flags bei Aktivierung als auch bei Deaktivierung abdecken.
  • Sie müssen die offiziellen Mechanismen verwenden, um Flag-Werte während des Tests festzulegen.
  • xTS-Tests sollten keine Flag-Werte in Tests überschreiben.

Im nächsten Abschnitt werden die offiziellen Mechanismen beschrieben, die Sie verwenden müssen, um diese Richtlinien einzuhalten.

Gekennzeichneten Code testen

Testszenario Verwendeter Mechanismus
Lokale Tests bei häufigen Änderungen von Flag-Werten Android Debug Bridge, wie unter Flag-Wert zur Laufzeit ändern beschrieben
Lokale Tests, wenn sich Flag-Werte nicht oft ändern Datei mit Flag-Werten, wie unter Werte für das Flag für die Einführung von Funktionen 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
Ende-zu-Ende- oder Unit-Tests, bei denen sich die Flag-Werte nicht ändern können CheckFlagsRule, wie unter Ende-zu-Ende- oder Unittests erstellen, in 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, legt diese Flags in der Gerätekonfiguration vor der Testausführung fest und stellt Flags nach der Ausführung wieder her.

Sie können die Funktionalität der Klasse FeatureFlagTargetPreparer auf Testmodulebene und Testkonfigurationsebene anwenden.

FeatureFlagTargetPreparer in einer Testmodulkonfiguration anwenden

Wenn Sie FeatureFlagTargetPreparer in einer Testmodulkonfiguration anwenden möchten, fügen Sie FeatureFlagTargetPreparer und Überschreibungen für Flag-Werte in die Konfigurationsdatei des Testmoduls AndroidTest.xml 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.

Parametrisierte Testmodule basierend auf Flag-Status erstellen

So erstellen Sie parametrisierte Testmodule basierend auf Flag-Status:

  1. Fügen Sie FeatureFlagTargetPreparer in die Konfigurationsdatei des Testmoduls AndroidTest.xml 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-Build-Datei 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, wobei name immer auf flag-value und value auf namespace/aconfigPackage.flagName=true|false gesetzt ist.

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 Annotationen @EnableFlags und @DisableFlags, um alle Codezweige zu testen.
  2. Verwenden Sie die Methode SetFlagsRule.ClassRule, um häufige Testfehler zu vermeiden.
  3. Verwenden Sie FlagsParameterization, um Ihre Klassen mit einer Vielzahl von Flag-Konfigurationen zu testen.

Alle Codezweige testen

Bei Projekten, in denen über die statische Klasse auf Flags zugegriffen 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 zum Überschreiben von Flag-Werten. Informationen dazu, wie SetFlagsRule Standardwerte ermittelt, finden Sie unter Standardwerte für Geräte.
  • @EnableFlags ist eine Anmerkung, die eine beliebige Anzahl von Flag-Namen akzeptiert. Verwenden Sie @DisableFlags, um Flags zu deaktivieren. Sie können diese Anmerkungen auf eine Methode oder eine Klasse anwenden.

Legen Sie Flag-Werte für den gesamten Testprozess fest. Beginnen Sie mit SetFlagsRule, das vor allen @Before-annotated-Einrichtungsmethoden im Test steht. Die Flag-Werte werden nach Abschluss von SetFlagsRule auf ihren vorherigen Zustand zurückgesetzt. Das ist nach allen Einrichtungsmethoden, die mit @After annotiert sind.

Prüfen, ob die Flags richtig gesetzt sind

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

Verwenden Sie die Methode SetFlagsRule.ClassRule, damit Test-Fixtures erst erstellt werden, wenn eine mit @Before annotierte Einrichtungsmethode aufgerufen wird. So wird sichergestellt, dass Test-Fixtures mit dem richtigen Klassenwert 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 das 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 die gesamte Klasse ein Flag aktiviert werden muss, verschieben Sie die Annotation @EnableFlags auf Klassenebene (vor die Klassendeklaration). Durch das Verschieben der Annotation auf Klassenebene kann SetFlagsRule.ClassRule dafür sorgen, dass das Flag im Konstruktor der Testklasse oder in Methoden, die mit @BeforeClass oder @AfterClass annotiert sind, richtig gesetzt wird.

Tests mit mehreren Flag-Konfigurationen ausführen

Da Sie Flag-Werte für jeden Test einzeln festlegen können, können Sie auch die Parametrisierung verwenden, um Tests mit mehreren 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() {...}
}

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

Wenn die Parametrisierung hinzugefügt wird, erstellt die Methode FlagsParameterization.allCombinationsOf alle möglichen Kombinationen der Flags FLAG_FOO und FLAG_BAR:

  • 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, werden mit den Anmerkungen @DisableFlags und @EnableFlags Flag-Werte basierend auf Parameterbedingungen geändert. legacyBarLogic wird beispielsweise nur ausgeführt, wenn FLAG_BAR deaktiviert ist. Das ist bei zwei der vier Flag-Kombinationen der Fall. Die legacyBarLogic wird für die anderen beiden Kombinationen übersprungen.

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

  • FlagsParameterization.allCombinationsOf(String...) führt 2^n Ausführungen jedes Tests aus. Mit einem Flag werden beispielsweise 2 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 2 Tests aus, vier Flags führen 5 Tests aus.

Unittests erstellen (C und C++)

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

  1. Fügen Sie in Ihre 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 Testläufe 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:

    • Statt 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 Veröffentlichung von Funktionen, die die aktivierte Bedingung 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 Bedingung „disabled“ 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 Konfigurationsdateien 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 für Flags, die standardmäßig in der Gerätekonfiguration festgelegt sind.
  3. Fügen Sie in Ihrer Android.bp-Build-Datei die 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 diesem 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

Für Testläufe, 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 gezeigt, wie Sie einen End-to-End- oder Unittest erstellen und ausführen, in dem Flag-Werte nicht überschrieben werden können:

  1. Verwenden Sie in Ihrem Testcode CheckFlagsRule, um 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 Hostseitentest 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 jflag-unit und aconfig-generierte Bibliotheken dem Abschnitt static_libs der Build-Datei für Ihren Test 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, lautet das Testergebnis:

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

    Andernfalls lautet das Testergebnis:

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

Gerätestandardwerte

Das 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, wird der Überschreibungswert als Standardwert für SetFlagsRule verwendet.

Wenn derselbe Test mit 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 auf ihren ursprünglichen FeatureFlagsImpl-Wert zurück, damit sie keine Nebeneffekte auf andere Testmethoden und -klassen hat.