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

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

  • Ваши тесты должны охватывать как включенное, так и выключенное поведение флага.
  • Для установки значений флагов во время тестирования необходимо использовать официальные механизмы.
  • Тесты 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 , а valuenamespace/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 используется для добавления зависимости `flags-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_FOO равно true , а FLAG_BAR равно false
  • FLAG_FOO имеет значение false , а 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 запусков каждого теста. Например, один флаг запускает 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 и 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) используется для флагов, определенных в файлах 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 , чтобы избежать побочных эффектов для других методов и классов тестирования.