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

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

  • Ваши тесты должны охватывать как включенное, так и выключенное поведение флага.
  • Для установки значений флагов во время тестирования необходимо использовать официальные механизмы.
  • Тесты 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 — это аннотация, используемая для добавления зависимости флаг-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_FOOtrue и FLAG_BARtrue
  • FLAG_FOOtrue , FLAG_BARfalse
  • FLAG_FOOfalse , а FLAG_BARtrue
  • FLAG_FOO — false, FLAG_BARfalse

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

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

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

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

Создание модульных тестов (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_WITH_FLAGS и TEST_F_WITH_FLAGS используются вместо макросов TEST и TEST_F .
    • 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 , чтобы не оказывать побочных эффектов на другие тестовые методы и классы.