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

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

  • 테스트는 플래그의 사용 설정 및 사용 중지 동작을 모두 다루어야 합니다.
  • 테스트 중에 플래그 값을 설정하려면 공식 메커니즘을 사용해야 합니다.
  • 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로 설정됩니다.
  • optionname가 항상 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 주석이 있는 메서드 중에 플래그가 올바르게 설정되었는지 확인할 수 없습니다.

테스트 피처가 올바른 클래스 값으로 생성되도록 하려면 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 클래스 규칙을 추가하면 DemoClass의 생성자가 FLAG_FLAG_FOO를 읽을 때 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배 실행하고 플래그 4개는 테스트를 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로 복원하여 다른 테스트 메서드와 클래스에 부작용이 없도록 합니다.