ทดสอบโค้ดภายใน Flag การเปิดตัวฟีเจอร์

การเปิดตัวฟีเจอร์ Flag สำหรับการเปิดตัวฟีเจอร์ทำให้เกิดนโยบายการทดสอบใหม่ ที่คุณต้องปฏิบัติตาม ดังนี้

  • การทดสอบต้องครอบคลุมทั้งลักษณะการทำงานที่เปิดใช้และปิดใช้ของ Flag
  • คุณต้องใช้กลไกอย่างเป็นทางการเพื่อตั้งค่าสถานะระหว่างการทดสอบ
  • การทดสอบ xTS ไม่ควรกําหนดค่าสถานะในการทดสอบใหม่

ส่วนถัดไปจะอธิบายกลไกอย่างเป็นทางการที่คุณต้องใช้เพื่อปฏิบัติตาม นโยบายเหล่านี้

ทดสอบโค้ดที่ถูกแจ้ง

สถานการณ์การทดสอบ กลไกที่ใช้
การทดสอบในเครื่องเมื่อค่าสถานะเปลี่ยนแปลงบ่อย Android Debug Bridge ตามที่อธิบายไว้ใน เปลี่ยนค่าของฟีเจอร์แฟลกขณะรันไทม์
การทดสอบในเครื่องเมื่อค่าสถานะไม่เปลี่ยนแปลงบ่อย ไฟล์ค่าแฟล็กตามที่อธิบายไว้ในตั้งค่าแฟล็กการเปิดตัวฟีเจอร์
การทดสอบแบบครบวงจรที่ค่าของ Flag เปลี่ยนแปลง FeatureFlagTargetPreparer ตามที่อธิบายไว้ในสร้างการทดสอบจากต้นทางถึงปลายทาง
การทดสอบหน่วยที่ค่าแฟล็กมีการเปลี่ยนแปลง SetFlagsRule โดยมี @EnableFlags และ @DisableFlags ตามที่อธิบายไว้ในสร้างการทดสอบหน่วย (Java และ Kotlin) หรือ สร้างการทดสอบหน่วย (C และ C++)
การทดสอบแบบครบวงจรหรือการทดสอบหน่วยที่ค่าสถานะเปลี่ยนแปลงไม่ได้ CheckFlagsRule ตามที่อธิบายไว้ในสร้างการทดสอบแบบครบวงจรหรือการทดสอบหน่วยที่ค่าของ Flag ไม่เปลี่ยนแปลง

สร้างการทดสอบแบบครบวงจร

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

หากต้องการเขียน Unit Test อัตโนมัติในโค้ดเบสขนาดใหญ่ที่มี Flag จำนวนมาก ให้ทำตามขั้นตอนต่อไปนี้

  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 คือคำอธิบายประกอบที่ใช้เพื่อเพิ่มทรัพยากร Dependency ของ flag-JUnit ของคลาส SetFlagsRule
  • SetFlagsRule คือคลาสตัวช่วยที่ใช้ในการลบล้างค่าสถานะ ดูข้อมูลเกี่ยวกับวิธีที่ SetFlagsRule กําหนดค่าเริ่มต้นได้ที่ค่าเริ่มต้นของอุปกรณ์
  • @EnableFlags คือคำอธิบายประกอบที่ยอมรับชื่อฟีเจอร์ จำนวนเท่าใดก็ได้ เมื่อปิดใช้ฟีเจอร์ทดลอง ให้ใช้ @DisableFlags คุณใช้ คำอธิบายประกอบ เหล่านี้กับเมธอดหรือคลาสก็ได้

ตั้งค่าแฟล็กสำหรับกระบวนการทดสอบทั้งหมด โดยเริ่มจาก SetFlagsRule ซึ่งอยู่ก่อนวิธีการตั้งค่าที่ใส่คำอธิบายประกอบ @Before ในการทดสอบ ค่าสถานะจะกลับไปเป็นสถานะก่อนหน้าเมื่อ SetFlagsRule เสร็จสิ้น ซึ่งจะเกิดขึ้นหลังจากวิธีการตั้งค่าที่ใส่คำอธิบายประกอบ @After

ฟีเจอร์ในส่วนถัดไปจะแก้ไขปัญหานี้

ตรวจสอบว่าได้ตั้งค่าสถานะอย่างถูกต้อง

ดังที่กล่าวไว้ก่อนหน้านี้ SetFlagsRule ใช้กับคำอธิบายประกอบ @Rule JUnit ซึ่งหมายความว่า 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 class rule test_flag_foo_turned_on จะล้มเหลวก่อนเรียกใช้เมื่อตัวสร้างของ DemoClass อ่าน FLAG_FLAG_FOO

หากทั้งชั้นเรียนต้องเปิดใช้ Flag ให้ย้าย@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 แต่ไม่มีการกำหนดพารามิเตอร์ คลาสนี้จะเรียกใช้การทดสอบ 3 รายการ (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_BAR คือ true
  • FLAG_FOO เป็นเท็จและ FLAG_BAR เป็น false

แทนที่จะเปลี่ยนค่าสถานะโดยตรง คำอธิบายประกอบ @DisableFlags และ @EnableFlags จะแก้ไขค่าสถานะตามเงื่อนไขของพารามิเตอร์ ตัวอย่างเช่น legacyBarLogic จะทํางานเมื่อปิดใช้ FLAG_BAR เท่านั้น ซึ่งจะเกิดขึ้นใน การผสมผสานของ 2 ใน 4 ของฟีเจอร์ ระบบจะข้าม legacyBarLogic สำหรับการผสมผสานอีก 2 รายการ

การสร้างพารามิเตอร์สำหรับฟีเจอร์แฟล็กทำได้ 2 วิธี ดังนี้

  • FlagsParameterization.allCombinationsOf(String...) จะดำเนินการทดสอบแต่ละรายการ 2^n ครั้ง เช่น แฟล็ก 1 รายการจะเรียกใช้การทดสอบ 2 เท่า หรือแฟล็ก 4 รายการจะเรียกใช้การทดสอบ 16 เท่า

  • FlagsParameterization.progressionOf(String...) จะเรียกใช้การทดสอบแต่ละรายการ n+1 ครั้ง เช่น แฟล็ก 1 รายการจะเรียกใช้การทดสอบ 2 รายการ และแฟล็ก 4 รายการจะเรียกใช้แฟล็ก 5 รายการ

สร้างการทดสอบหน่วย (C และ C++)

AOSP มีมาโครค่าแฟล็กสำหรับเทสต์ C และ C++ ที่เขียนในเฟรมเวิร์ก GoogleTest

  1. ในแหล่งที่มาของการทดสอบ ให้รวมคำจำกัดความของมาโครและไลบรารีที่สร้างโดย aconfig ดังนี้

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. ในแหล่งข้อมูลทดสอบ ให้ใช้ TEST_WITH_FLAGS และ TEST_F_WITH_FLAGS แทนมาโคร TEST และ TESTF สำหรับกรณีทดสอบ

    #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 และ ไลบรารีมาโครที่เกี่ยวข้องเป็นทรัพยากร Dependency ของการทดสอบ

    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 เดิม เพื่อไม่ให้ส่งผลกระทบต่อ เมธอดและคลาสการทดสอบอื่นๆ