기능 출시 플래그 내에서 코드 테스트

기능 출시 플래그가 도입됨에 따라 준수해야 하는 새로운 테스트 정책이 있습니다.

  • 테스트는 플래그의 사용 설정 및 사용 중지 동작을 모두 다뤄야 합니다.
  • 테스트 중에 공식 메커니즘을 사용하여 플래그 값을 설정해야 합니다.
  • xTS 테스트는 테스트에서 플래그 값을 재정의하면 안 됩니다.

다음 섹션에서는 이러한 정책을 준수하기 위해 사용해야 하는 공식 메커니즘을 설명합니다.

플래그가 지정된 코드 테스트

테스트 시나리오 사용된 메커니즘
플래그 값이 자주 변경될 때 로컬 테스트 런타임 시 플래그 값 변경에 설명된 Android 디버그 브리지
플래그 값이 자주 변경되지 않는 경우의 로컬 테스트 기능 출시 플래그 값 설정에 설명된 플래그 값 파일
플래그 값이 변경되는 엔드 투 엔드 테스트 FeatureFlagTargetPreparer(엔드 투 엔드 테스트 만들기에 설명됨)
플래그 값이 변경되는 단위 테스트 단위 테스트 만들기 (Java 및 Kotlin) 또는 단위 테스트 만들기 (C 및 C++)에 설명된 대로 @EnableFlags@DisableFlags를 사용한 SetFlagsRule
플래그 값을 변경할 수 없는 엔드 투 엔드 또는 단위 테스트 플래그 값이 변경되지 않는 엔드 투 엔드 또는 단위 테스트 만들기에 설명된 대로 CheckFlagsRule

엔드 투 엔드 테스트 만들기

AOSP는 기기에서 엔드 투 엔드 테스트를 지원하는 FeatureFlagTargetPreparer라는 클래스를 제공합니다. 이 클래스는 플래그 값 재정의를 입력으로 허용하고, 테스트 실행 전에 기기 구성에서 이러한 플래그를 설정하며, 실행 후 플래그를 복원합니다.

테스트 모듈 및 테스트 구성 수준에서 FeatureFlagTargetPreparer 클래스의 기능을 적용할 수 있습니다.

테스트 모듈 구성에서 FeatureFlagTargetPreparer 적용

테스트 모듈 구성에서 FeatureFlagTargetPreparer를 적용하려면 AndroidTest.xml 테스트 모듈 구성 파일에 FeatureFlagTargetPreparer 및 플래그 값 재정의를 포함하세요.

  <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. AndroidTest.xml 테스트 모듈 구성 파일에 FeatureFlagTargetPreparer를 포함합니다.

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Android.bp 빌드 파일의 test_module_config 섹션에서 플래그 값 옵션을 지정합니다.

    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로 설정되고 valuenamespace/aconfigPackage.flagName=true|false로 설정됩니다.

단위 테스트 만들기 (Java 및 Kotlin)

이 섹션에서는 Java 및 Kotlin 테스트에서 클래스 및 메서드 수준 (테스트별)으로 aconfig 플래그 값을 재정의하는 접근 방식을 설명합니다.

플래그가 많은 대규모 코드베이스에서 자동화된 단위 테스트를 작성하려면 다음 단계를 따르세요.

  1. @EnableFlags@DisableFlags 주석과 함께 SetFlagsRule 클래스를 사용하여 모든 코드 브랜치를 테스트합니다.
  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() {
    ...
    }

각각의 의미는 다음과 같습니다.

  • @RuleSetFlagsRule 클래스의 플래그-JUnit 종속 항목을 추가하는 데 사용되는 주석입니다.
  • SetFlagsRule는 플래그 값을 재정의하기 위해 제공되는 도우미 클래스입니다. SetFlagsRule에서 기본값을 결정하는 방법에 관한 자세한 내용은 기기 기본값을 참고하세요.
  • @EnableFlags는 원하는 수의 플래그 이름을 허용하는 주석입니다. 플래그를 사용 중지할 때는 @DisableFlags를 사용합니다. 이러한 주석은 메서드 또는 클래스에 적용할 수 있습니다.

테스트의 @Before 주석이 달린 설정 메서드보다 먼저인 SetFlagsRule부터 시작하여 전체 테스트 프로세스의 플래그 값을 설정합니다. 플래그 값은 SetFlagsRule가 완료될 때 이전 상태로 돌아갑니다. 이는 @After 주석이 달린 설정 메서드 이후입니다.

플래그가 올바르게 설정되어 있는지 확인

앞서 언급한 것처럼 SetFlagsRule는 JUnit @Rule 주석과 함께 사용됩니다. 즉, SetFlagsRule는 테스트 클래스의 생성자 또는 @BeforeClass 또는 @AfterClass 주석이 달린 메서드 중에 플래그가 올바르게 설정되었는지 확인할 수 없습니다.

테스트 고정 장치가 올바른 클래스 값으로 생성되도록 하려면 @Before 주석이 달린 설정 메서드가 실행될 때까지 고정 장치가 생성되지 않도록 SetFlagsRule.ClassRule 메서드를 사용하세요.

  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 클래스 규칙을 추가하면 FLAG_FLAG_FOODemoClass의 생성자에 의해 읽힐 때 실행 전에 test_flag_foo_turned_on이 실패합니다.

전체 클래스에 플래그를 사용 설정해야 하는 경우 @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_FOOFLAG_BAR 값이 무엇으로 설정되어 있든 실행됩니다.

파라미터화가 추가되면 FlagsParameterization.allCombinationsOf 메서드가 FLAG_FOOFLAG_BAR 플래그의 가능한 모든 조합을 만듭니다.

  • FLAG_FOOtrue이고 FLAG_BARtrue입니다.
  • FLAG_FOOtrue이고 FLAG_BARfalse입니다.
  • FLAG_FOOfalse이고 FLAG_BARtrue입니다.
  • FLAG_FOO이 false이고 FLAG_BARfalse인 경우

플래그 값을 직접 변경하는 대신 @DisableFlags@EnableFlags 주석은 매개변수 조건에 따라 플래그 값을 수정합니다. 예를 들어 legacyBarLogicFLAG_BAR가 사용 중지된 경우에만 실행되며 이는 4가지 플래그 조합 중 2가지에서 발생합니다. 다른 두 조합에서는 legacyBarLogic가 건너뜁니다.

플래그의 매개변수화를 만드는 방법에는 두 가지가 있습니다.

  • FlagsParameterization.allCombinationsOf(String...)는 각 테스트를 2^n회 실행합니다. 예를 들어 플래그 하나는 2배 테스트를 실행하고 플래그 4개는 16배 테스트를 실행합니다.

  • FlagsParameterization.progressionOf(String...)은 각 테스트를 n+1회 실행합니다. 예를 들어 한 플래그는 2배 테스트를 실행하고 네 플래그는 5배 플래그를 실행합니다.

단위 테스트 만들기 (C 및 C++)

AOSP에는 GoogleTest 프레임워크로 작성된 C 및 C++ 테스트의 플래그 값 매크로가 포함됩니다.

  1. 테스트 소스에 매크로 정의와 aconfig 생성 라이브러리를 포함합니다.

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. 테스트 소스에서 테스트 사례에 TESTTESTF 매크로를 사용하는 대신 TEST_WITH_FLAGSTEST_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");
    }
    

    각각의 의미는 다음과 같습니다.

    • TESTTEST_F 매크로 대신 TEST_WITH_FLAGSTEST_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)
    

플래그 값이 변경되지 않는 엔드 투 엔드 또는 단위 테스트 만들기

플래그를 재정의할 수 없고 현재 플래그 상태를 기반으로 하는 경우에만 테스트를 필터링할 수 있는 테스트 사례의 경우 RequiresFlagsEnabledRequiresFlagsDisabled 주석과 함께 CheckFlagsRule 규칙을 사용하세요.

다음 단계에서는 플래그 값을 재정의할 수 없는 엔드 투 엔드 또는 단위 테스트를 만들고 실행하는 방법을 보여줍니다.

  1. 테스트 코드에서 CheckFlagsRule를 사용하여 테스트 필터링을 적용합니다. 또한 Java 주석 RequiresFlagsEnabledRequiredFlagsDisabled를 사용하여 테스트의 플래그 요구사항을 지정합니다.

    기기 측 테스트는 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. 테스트의 빌드 파일에 있는 static_libs 섹션에 jflag-unit 및 aconfig 생성 라이브러리를 추가합니다.

    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로 명시적으로 설정되지 않은 플래그의 값이 달라질 수 있습니다.

각 테스트 후 SetFlagsRuleFlagsFeatureFlags 인스턴스를 원래 FeatureFlagsImpl로 복원하므로 다른 테스트 메서드와 클래스에 부작용이 없습니다.