Тестовый код внутри флагов запуска функции

С введением флагов запуска функций появились новые политики тестирования, которых вы должны придерживаться:

  • Ваши тесты должны охватывать как включенное, так и отключенное поведение флага.
  • Вы должны использовать официальные механизмы для установки значений флагов во время тестирования.
  • Тесты xTS не должны переопределять значения флагов в тестах.

В следующем разделе представлены официальные механизмы, которые вы должны использовать для соблюдения этих политик.

Проверьте помеченный код

Тестовый сценарий Используемый механизм
Локальное тестирование, когда значения флагов часто меняются Мост отладки Android, как описано в разделе «Изменение значения флага во время выполнения».
Локальное тестирование, когда значения флагов меняются нечасто Файл значений флагов, как описано в разделе «Установка значений флагов запуска функции».
Сквозное тестирование, при котором значения флагов меняются. FeatureFlagTargetPreparer как описано в разделе Создание сквозных тестов.
Модульное тестирование, при котором меняются значения флагов SetFlagsRule с @EnableFlags и @DisableFlags как описано в разделах Создание модульных тестов (Java и Kotlin) или Создание модульных тестов (C и C++).
Сквозное или модульное тестирование, при котором значения флагов не могут меняться. CheckFlagsRule , как описано в разделе Создание сквозных или модульных тестов, в которых значения флагов не изменяются.

Создавайте сквозные тесты

AOSP предоставляет класс FeatureFlagTargetPreparer , который обеспечивает сквозное тестирование на устройстве. Этот класс принимает переопределения значений флагов в качестве входных данных, устанавливает эти флаги в конфигурации устройств перед выполнением теста и восстанавливает флаги после выполнения.

Функциональность класса FeatureFlagTargetPreparer можно применить на уровне тестового модуля и конфигурации теста.

Примените FeatureFlagTargetPreparer в конфигурации тестового модуля.

Чтобы применить FeatureFlagTargetPreparer в конфигурации тестового модуля, включите FeatureFlagTargetPreparer и переопределения значений флагов в файл конфигурации тестового модуля AndroidTest.xml :

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

Где:

  • target.preparer class всегда имеет значение com.android.tradefed.targetprep.FeatureFlagTargetPreparer .
  • option — это переопределение флага, где для name всегда установлено flag-value , а для value установлено значение namespace/aconfigPackage.flagName=true|false .

Создавайте параметризованные тестовые модули на основе состояний флагов.

Чтобы создать параметризованные тестовые модули на основе состояний флагов:

  1. Включите FeatureFlagTargetPreparer в файл конфигурации тестового модуля AndroidTest.xml :

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Укажите параметры значения флага в разделе test_module_config файла сборки 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"},
        ],
    }
    

    Поле options содержит переопределения флагов, где для name всегда установлено flag-value , а для value установлено значение namespace/aconfigPackage.flagName=true|false .

Создание модульных тестов (Java и Kotlin)

В этом разделе описывается подход к переопределению значений флагов aconfig на уровне класса и метода (для каждого теста) в тестах Java и Kotlin.

Чтобы написать автоматизированные модульные тесты в большой базе кода с большим количеством флагов, выполните следующие действия:

  1. Используйте класс SetFlagsRule с аннотациями @EnableFlags и @DisableFlags чтобы протестировать все ветки кода.
  2. Используйте метод SetFlagsRule.ClassRule , чтобы избежать распространенных ошибок тестирования.
  3. Используйте FlagsParameterization для тестирования своих классов с использованием широкого набора конфигураций флагов.

Протестируйте все ветки кода

Для проектов, использующих статический класс для доступа к флагам, предоставляется вспомогательный класс SetFlagsRule для переопределения значений флагов. В следующем фрагменте кода показано, как включить SetFlagsRule и включить несколько флагов одновременно:

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

Где:

  • @Rule — это аннотация, используемая для добавления зависимости flag-JUnit класса SetFlagsRule .
  • SetFlagsRule — это вспомогательный класс, предназначенный для переопределения значений флагов. Сведения о том, как SetFlagsRule определяет значения по умолчанию, см. в разделе Значения устройства по умолчанию .
  • @EnableFlags — это аннотация, которая принимает произвольное количество имен флагов. При отключении флагов используйте @DisableFlags . Вы можете применить эти аннотации либо к методу, либо к классу.

Установите значения флагов для всего процесса тестирования, начиная с SetFlagsRule , который предшествует любым методам настройки с аннотацией @Before в тесте. Значения флагов возвращаются в свое предыдущее состояние после завершения SetFlagsRule , то есть после любых методов установки с аннотацией @After .

Убедитесь, что флаги установлены правильно

Как упоминалось ранее, SetFlagsRule используется с аннотацией JUnit @Rule , что означает, что SetFlagsRule не может гарантировать правильную установку ваших флагов во время конструктора тестового класса или любых аннотированных методов @BeforeClass или @AfterClass .

Чтобы гарантировать, что тестовые фикстуры созданы с правильным значением класса, используйте метод SetFlagsRule.ClassRule , чтобы ваши фикстуры не создавались до тех пор, пока не будет создан метод установки с аннотацией @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() {
      ...
    }
  }

При добавлении правила класса SetFlagsRule.ClassRule test_flag_foo_turned_on завершается с ошибкой перед запуском, когда FLAG_FLAG_FOO читается конструктором DemoClass .

Если весь ваш класс требует включения флага, переместите аннотацию @EnableFlags на уровень класса (перед объявлением класса). Перемещение аннотации на уровень класса позволяет SetFlagsRule.ClassRule гарантировать правильную установку флага во время конструктора тестового класса или во время любых аннотированных методов @BeforeClass или @AfterClass .

Запуск тестов для нескольких конфигураций флагов

Поскольку вы можете устанавливать значения флагов для каждого теста, вы также можете использовать параметризацию для запуска тестов в нескольких конфигурациях флагов:

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

Обратите внимание, что с SetFlagsRule , но без параметризации, этот класс выполняет три теста ( fooLogic , legacyBarLogic и newBarLogic ). Метод fooLogic выполняется с любыми значениями FLAG_FOO и FLAG_BAR установленными на устройстве.

При добавлении параметризации метод FlagsParameterization.allCombinationsOf создает все возможные комбинации флагов FLAG_FOO и FLAG_BAR :

  • FLAG_FOO true , а FLAG_BAR true
  • FLAG_FOOtrue , а FLAG_BARfalse
  • FLAG_FOO имеет значение false , а FLAG_BAR имеет true
  • FLAG_FOO имеет значение false, а FLAG_BAR имеет false

Вместо прямого изменения значений флагов аннотации @DisableFlags и @EnableFlags изменяют значения флагов на основе условий параметров. Например, legacyBarLogic запускается только тогда, когда FLAG_BAR отключен, что происходит в двух из четырех комбинаций флагов. legacyBarLogic пропускается для двух других комбинаций.

Существует два метода создания параметризации ваших флагов:

  • FlagsParameterization.allCombinationsOf(String...) выполняет 2^n прогонов каждого теста. Например, один флаг запускает 2 теста, а четыре флага запускают 16 тестов.

  • FlagsParameterization.progressionOf(String...) выполняет n+1 прогон каждого теста. Например, один флаг запускает 2x тестов, а четыре флага запускают 5x флагов.

Создание модульных тестов (C и C++)

AOSP включает макросы значений флагов для тестов C и C++, написанные в среде GoogleTest.

  1. В исходный код теста включите определения макросов и библиотеки, сгенерированные aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. В исходном коде теста вместо использования макросов TEST и TESTF для тестовых случаев используйте TEST_WITH_FLAGS и 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");
    }
    

    Где:

    • Вместо макросов TEST и TEST_F используются макросы TEST_WITH_FLAGS и TEST_F_WITH_FLAGS .
    • REQUIRES_FLAGS_ENABLED определяет набор флагов выпуска функций, которые должны соответствовать условию включения. Вы можете записать эти флаги в макросах ACONFIG_FLAG или LEGACY_FLAG .
    • REQUIRES_FLAGS_DISABLED определяет набор флагов функций, которые должны соответствовать условию отключения. Вы можете записать эти флаги в макросах ACONFIG_FLAG или LEGACY_FLAG .
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) — это макрос, используемый для флагов, определенных в файлах конфигурации. Этот макрос принимает пространство имен ( TEST_NS ) и имя флага ( readwrite_enabled_flag ).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) — это макрос, используемый для флагов, установленных в конфигурации устройства по умолчанию.
  3. В файл сборки Android.bp добавьте библиотеки, сгенерированные aconfig, и соответствующие библиотеки макросов в качестве тестовой зависимости:

    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. Запустите тесты локально с помощью этой команды:

    atest FlagMacrosTests
    

    Если флаг my_namespace.android.myflag.tests.my_flag отключен, результат теста будет следующим:

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

    Если флаг my_namespace.android.myflag.tests.my_flag включен, результат теста будет следующим:

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

Создавайте сквозные или модульные тесты, в которых значения флагов не меняются.

Для тестовых случаев, в которых вы не можете переопределить флаги и можете фильтровать тесты, только если они основаны на текущем состоянии флага, используйте правило CheckFlagsRule с аннотациями RequiresFlagsEnabled и RequiresFlagsDisabled .

Следующие шаги показывают, как создать и запустить сквозной или модульный тест, в котором значения флагов не могут быть переопределены:

  1. В тестовом коде используйте CheckFlagsRule , чтобы применить тестовую фильтрацию. Кроме того, используйте аннотации Java RequiresFlagsEnabled и RequiredFlagsDisabled чтобы указать требования к флагам для вашего теста.

    Тест на стороне устройства использует класс 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() {}
    }
    

    Тест на стороне хоста использует класс 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. Добавьте библиотеки jflag-unit и aconfig в раздел static_libs файла сборки для вашего теста:

    android_test {
        name: "FlagAnnotationTests",
        srcs: ["*.java"],
        static_libs: [
            "androidx.test.rules",
            "my_aconfig_lib",
            "flag-junit",
            "platform-test-annotations",
        ],
        test_suites: ["general-tests"],
    }
    
  3. Используйте следующую команду для локального запуска теста:

    atest FlagAnnotationTests
    

    Если флаг Flags.FLAG_FLAG_NAME_1 отключен, результат проверки будет следующим:

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

    В противном случае результат теста:

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

Значения устройства по умолчанию

Инициализированный SetFlagsRule использует значения флагов устройства. Если значение флага на устройстве не переопределено, например, с помощью adb, то значение по умолчанию совпадает с конфигурацией выпуска сборки. Если значение на устройстве было переопределено, SetFlagsRule использует значение переопределения по умолчанию.

Если один и тот же тест выполняется в разных конфигурациях выпуска, значения флагов, не установленных явно с помощью SetFlagsRule могут различаться.

После каждого теста SetFlagsRule восстанавливает экземпляр FeatureFlags в Flags до исходного FeatureFlagsImpl , чтобы он не оказывал побочных эффектов на другие методы и классы тестирования.