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

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

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

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

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

Тестовый сценарий Механизм используемый
Локальное тестирование при частом изменении значений флагов Android debug bridge, как обсуждалось в разделе Изменение значения флага во время выполнения
Локальное тестирование, когда значения флагов меняются нечасто Файл значений флагов, как обсуждалось в разделе Установка значений флагов запуска функций
Сквозное тестирование, при котором значения флагов меняются 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_BAR имеет значение false

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

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

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

  • 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_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) — это макрос, используемый для флагов, определенных в файлах aconfig. Этот макрос принимает пространство имен ( 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-generated в раздел 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 , чтобы не оказывать побочных эффектов на другие методы и классы теста.