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

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

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

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

ทดสอบโค้ดที่ได้รับแจ้ง

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

สร้างการทดสอบตั้งแต่ต้นจนจบ

AOSP มีคลาสชื่อ FeatureFlagTargetPreparer ซึ่งเปิดใช้การทดสอบจากต้นทางถึงปลายทางในอุปกรณ์ คลาสนี้ยอมรับการลบล้างค่า Flag เป็นอินพุต ตั้งค่า Flag ดังกล่าวในการกำหนดค่าอุปกรณ์ก่อนการดำเนินการทดสอบ และคืนค่า Flag หลังการดำเนินการ

คุณจะใช้ฟังก์ชันการทำงานของคลาส FeatureFlagTargetPreparer ที่โมดูลการทดสอบและระดับการกำหนดค่าการทดสอบได้

ใช้ FeatureFlagTargetPreparer ในการกำหนดค่าโมดูลทดสอบ

หากต้องการใช้ FeatureFlagTargetPreparer ในการกำหนดค่าโมดูลทดสอบ ให้ใส่ FeatureFlagTargetPreparer และการลบล้างค่า Flag ในไฟล์การกำหนดค่าโมดูลทดสอบ 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

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

ส่วนนี้อธิบายวิธีการลบล้างค่า Flag aconfig ที่ระดับคลาสและเมธอด (ต่อการทดสอบ) ในการทดสอบ Java และ Kotlin

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

  1. ใช้คลาส SetFlagsRule ที่มีคำอธิบายประกอบ @EnableFlags และ @DisableFlags เพื่อทดสอบสาขาโค้ดทั้งหมด
  2. ใช้เมธอด SetFlagsRule.ClassRule เพื่อหลีกเลี่ยงข้อบกพร่องการทดสอบที่พบบ่อย
  3. ใช้ FlagsParameterization เพื่อทดสอบชั้นเรียนที่มีชุดการกำหนดค่า Flag ต่างๆ ได้อย่างหลากหลาย

ทดสอบสาขาโค้ดทั้งหมด

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

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

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

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

หากต้องเปิดใช้ Flag กับทั้งคลาส ให้ย้ายคำอธิบายประกอบ @EnableFlags ไปที่ระดับคลาส (ก่อนการประกาศคลาส) การย้ายคำอธิบายประกอบไปยังระดับชั้นเรียนจะช่วยให้ SetFlagsRule.ClassRule ตรวจสอบได้ว่ามีการตั้งค่าแฟล็กอย่างถูกต้องในระหว่างเครื่องมือสร้างของคลาสการทดสอบ หรือระหว่างเมธอด @BeforeClass หรือ @AfterClass ที่มีคำอธิบายประกอบ

ทำการทดสอบในการกำหนดค่า Flag หลายรายการ

เนื่องจากคุณกำหนดค่าแฟล็กได้ตามการทดสอบแต่ละครั้ง คุณใช้พารามิเตอร์เพื่อทำการทดสอบในการกำหนดค่า Flag หลายรายการได้ด้วย ดังนี้

...
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_FOOtrueและFLAG_BARfalse
  • FLAG_FOO คือ false และ FLAG_BAR คือ true
  • FLAG_FOO เป็นเท็จและ FLAG_BAR มีค่าเป็น false

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

การสร้างการพารามิเตอร์สำหรับ Flag มี 2 วิธีดังนี้

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

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

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

AOSP มีมาโครค่า Flag สําหรับการทดสอบ 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 กำหนดชุด Flag ของรุ่นฟีเจอร์ที่ต้องเป็นไปตามเงื่อนไขที่เปิดใช้ ซึ่งคุณเขียนแฟล็กเหล่านี้ได้ในมาโคร ACONFIG_FLAG หรือ LEGACY_FLAG
    • REQUIRES_FLAGS_DISABLED กำหนดชุด Flag ฟีเจอร์ที่ต้องเป็นไปตามเงื่อนไข "ปิดใช้" ซึ่งคุณเขียนแฟล็กเหล่านี้ได้ในมาโคร 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 สร้างขึ้นและไลบรารีมาโครที่เกี่ยวข้องเป็นทรัพยากร 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
    

    หากปิดใช้ Flag 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)
    

สร้างการทดสอบแบบจุดต่อจุดหรือแบบหน่วยที่ค่าแฟล็กไม่มีการเปลี่ยนแปลง

สำหรับกรอบการทดสอบที่คุณลบล้างแฟล็กไม่ได้และกรองการทดสอบได้ก็ต่อเมื่อการทดสอบอิงตามสถานะ Flag ปัจจุบันเท่านั้น ให้ใช้กฎ CheckFlagsRule กับคำอธิบายประกอบ RequiresFlagsEnabled และ RequiresFlagsDisabled

ขั้นตอนต่อไปนี้แสดงวิธีสร้างและเรียกใช้การทดสอบจากต้นทางถึงปลายทางหรือการทดสอบหน่วยซึ่งไม่สามารถลบล้างค่า Flag ได้

  1. ในโค้ดทดสอบ ให้ใช้ CheckFlagsRule เพื่อใช้การกรองทดสอบ นอกจากนี้ ให้ใช้คำอธิบายประกอบของ Java RequiresFlagsEnabled และ RequiredFlagsDisabled เพื่อระบุข้อกำหนด Flag สำหรับการทดสอบของคุณ

    การทดสอบฝั่งอุปกรณ์จะใช้คลาส 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 เริ่มต้นจะใช้ค่าแฟล็กจากอุปกรณ์ หากไม่ได้ลบล้างค่า Flag ในอุปกรณ์ เช่น adb ค่าเริ่มต้นจะเหมือนกับการกำหนดค่าการเผยแพร่ของบิลด์ หากมีการลบล้างค่าในอุปกรณ์ SetFlagsRule จะใช้ค่าการลบล้างเป็นค่าเริ่มต้น

หากทำการทดสอบเดียวกันภายใต้การกำหนดค่ารุ่นที่แตกต่างกัน ค่าของ Flag ที่ไม่ได้ตั้งค่าอย่างชัดเจนด้วย SetFlagsRule อาจแตกต่างกันไป

หลังจากการทดสอบแต่ละครั้ง SetFlagsRule จะกู้คืนอินสแตนซ์ FeatureFlags ใน Flags กลับเป็น FeatureFlagsImpl เดิม เพื่อไม่ให้ส่งผลเสียต่อเมธอดและคลาสทดสอบอื่นๆ