Kiểm thử mã trong cờ phát hành tính năng

Khi giới thiệu cờ phát hành tính năng, bạn phải tuân thủ các chính sách thử nghiệm mới sau:

  • Quy trình kiểm thử của bạn phải bao gồm cả hành vi bật và tắt của cờ.
  • Bạn phải sử dụng các cơ chế chính thức để đặt giá trị cờ trong quá trình kiểm thử.
  • Các kiểm thử xTS không được ghi đè các giá trị cờ trong kiểm thử.

Phần tiếp theo cung cấp các cơ chế chính thức mà bạn phải sử dụng để tuân thủ các chính sách này.

Kiểm thử mã bị gắn cờ

Tình huống kiểm thử Cơ chế được sử dụng
Kiểm thử cục bộ khi giá trị cờ thường xuyên thay đổi Cầu gỡ lỗi Android như đã thảo luận trong phần Thay đổi giá trị của cờ trong thời gian chạy
Kiểm thử cục bộ khi giá trị cờ không thường xuyên thay đổi Tệp giá trị cờ như đã thảo luận trong phần Đặt giá trị cờ phát hành tính năng
Kiểm thử toàn diện khi giá trị cờ thay đổi FeatureFlagTargetPreparer như đã thảo luận trong phần Tạo kiểm thử toàn diện
Kiểm thử đơn vị khi giá trị cờ thay đổi SetFlagsRule với @EnableFlags@DisableFlags như đã thảo luận trong phần Tạo kiểm thử đơn vị (Java và Kotlin) hoặc Tạo kiểm thử đơn vị (C và C++)
Kiểm thử toàn diện hoặc kiểm thử đơn vị mà giá trị cờ không thể thay đổi CheckFlagsRule như đã thảo luận trong phần Tạo kiểm thử toàn diện hoặc kiểm thử đơn vị mà giá trị cờ không thay đổi

Tạo kiểm thử toàn diện

AOSP cung cấp một lớp có tên là FeatureFlagTargetPreparer, cho phép kiểm thử toàn diện trên một thiết bị. Lớp này chấp nhận giá trị cờ ghi đè làm đầu vào, đặt các cờ đó trong cấu hình thiết bị trước khi thực thi kiểm thử và khôi phục cờ sau khi thực thi.

Bạn có thể áp dụng chức năng của lớp FeatureFlagTargetPreparer ở cấp mô-đun kiểm thử và cấp cấu hình kiểm thử.

Áp dụng FeatureFlagTargetPreparer trong cấu hình mô-đun kiểm thử

Để áp dụng FeatureFlagTargetPreparer trong cấu hình mô-đun kiểm thử, hãy đưa FeatureFlagTargetPreparer và giá trị cờ ghi đè vào tệp cấu hình mô-đun kiểm thử 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>

Trong trường hợp:

  • target.preparer class luôn được đặt thành com.android.tradefed.targetprep.FeatureFlagTargetPreparer.
  • option là cờ ghi đè với name luôn được đặt thành flag-valuevalue được đặt thành namespace/aconfigPackage.flagName=true|false.

Tạo mô-đun kiểm thử có tham số dựa trên trạng thái cờ

Cách tạo mô-đun kiểm thử có tham số dựa trên trạng thái cờ:

  1. Đưa FeatureFlagTargetPreparer vào tệp cấu hình mô-đun kiểm thử AndroidTest.xml:

    <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
    
  2. Chỉ định các tuỳ chọn giá trị cờ trong phần test_module_config của tệp bản dựng 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"},
        ],
    }
    

    Trường options chứa các chế độ ghi đè cờ, trong đó name luôn được đặt thành flag-valuevalue được đặt thành namespace/aconfigPackage.flagName=true|false.

Tạo kiểm thử đơn vị (Java và Kotlin)

Phần này mô tả phương pháp ghi đè các giá trị cờ aconfig ở cấp lớp và cấp phương thức (mỗi lần kiểm thử) trong các kiểm thử Java và Kotlin.

Để viết mã kiểm thử đơn vị tự động trong một cơ sở mã lớn có nhiều cờ, hãy làm theo các bước sau:

  1. Sử dụng lớp SetFlagsRule với chú thích @EnableFlags@DisableFlags để kiểm thử tất cả các nhánh mã.
  2. Sử dụng phương thức SetFlagsRule.ClassRule để tránh các lỗi kiểm thử thường gặp.
  3. Sử dụng FlagsParameterization để kiểm thử các lớp của bạn trên một tập hợp rộng các cấu hình cờ.

Kiểm thử tất cả các nhánh mã

Đối với các dự án sử dụng lớp tĩnh để truy cập vào cờ, lớp trình trợ giúp SetFlagsRule được cung cấp để ghi đè các giá trị cờ. Đoạn mã sau đây cho biết cách đưa SetFlagsRule vào và bật một số cờ cùng một lúc:

  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() {
    ...
    }

Trong trường hợp:

  • @Rule là một chú thích dùng để thêm phần phụ thuộc flag-JUnit của lớp SetFlagsRule.
  • SetFlagsRule là lớp trợ giúp được cung cấp để ghi đè các giá trị cờ. Để biết thông tin về cách SetFlagsRule xác định giá trị mặc định, hãy xem phần Giá trị mặc định của thiết bị.
  • @EnableFlags là một chú thích chấp nhận số lượng tên cờ tuỳ ý. Khi tắt cờ, hãy sử dụng @DisableFlags. Bạn có thể áp dụng các chú thích này cho một phương thức hoặc một lớp.

Đặt giá trị cờ cho toàn bộ quy trình kiểm thử, bắt đầu bằng SetFlagsRule, trước mọi phương thức thiết lập được chú thích @Before trong kiểm thử. Các giá trị cờ sẽ trở về trạng thái trước đó khi SetFlagsRule kết thúc, tức là sau mọi phương thức thiết lập được chú thích @After.

Đảm bảo bạn đặt cờ đúng cách

Như đã đề cập trước đó, SetFlagsRule được sử dụng với chú thích @Rule của JUnit, nghĩa là SetFlagsRule không thể đảm bảo rằng cờ của bạn được đặt chính xác trong hàm khởi tạo của lớp kiểm thử hoặc bất kỳ phương thức nào được chú thích @BeforeClass hoặc @AfterClass.

Để đảm bảo rằng các môi trường thử nghiệm cố định được tạo bằng giá trị lớp chính xác, hãy sử dụng phương thức SetFlagsRule.ClassRule để các môi trường thử nghiệm cố định không được tạo cho đến khi có phương thức thiết lập được chú thích @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() {
      ...
    }
  }

Bằng cách thêm quy tắc lớp SetFlagsRule.ClassRule, test_flag_foo_turned_on sẽ không chạy được trước khi chạy khi hàm khởi tạo của DemoClass đọc FLAG_FLAG_FOO.

Nếu toàn bộ lớp của bạn cần bật cờ, hãy di chuyển chú thích @EnableFlags sang cấp lớp (trước phần khai báo lớp). Việc di chuyển chú thích lên cấp lớp cho phép SetFlagsRule.ClassRule đảm bảo cờ được đặt chính xác trong hàm khởi tạo của lớp kiểm thử hoặc trong bất kỳ phương thức nào được chú thích @BeforeClass hoặc @AfterClass.

Chạy kiểm thử trên nhiều cấu hình cờ

Vì bạn có thể đặt giá trị cờ trên cơ sở mỗi lần kiểm thử, nên bạn cũng có thể sử dụng tính năng tham số hoá để chạy kiểm thử trên nhiều cấu hình cờ:

...
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() {...}
}

Lưu ý rằng với SetFlagsRule, nhưng không có tham số, lớp này sẽ chạy 3 chương trình kiểm thử (fooLogic, legacyBarLogicnewBarLogic). Phương thức fooLogic sẽ chạy với bất kỳ giá trị nào của FLAG_FOOFLAG_BAR được đặt trên thiết bị.

Khi thêm thông số, phương thức FlagsParameterization.allCombinationsOf sẽ tạo tất cả các tổ hợp có thể có của cờ FLAG_FOOFLAG_BAR:

  • FLAG_FOOtrueFLAG_BARtrue
  • FLAG_FOOtrueFLAG_BARfalse
  • FLAG_FOOfalseFLAG_BARtrue
  • FLAG_FOO là false và FLAG_BARfalse

Thay vì trực tiếp thay đổi giá trị cờ, chú thích @DisableFlags@EnableFlags sẽ sửa đổi giá trị cờ dựa trên điều kiện tham số. Ví dụ: legacyBarLogic chỉ chạy khi FLAG_BAR bị tắt, điều này xảy ra trong hai trong số bốn tổ hợp cờ. legacyBarLogic sẽ bị bỏ qua đối với hai tổ hợp khác.

Có hai phương thức để tạo thông số cho cờ:

  • FlagsParameterization.allCombinationsOf(String...) thực thi 2^n lần chạy của mỗi quy trình kiểm thử. Ví dụ: một cờ chạy 2 lần kiểm thử hoặc 4 cờ chạy 16 lần kiểm thử.

  • FlagsParameterization.progressionOf(String...) thực thi n+1 lần chạy của mỗi bài kiểm thử. Ví dụ: một cờ chạy 2 lần kiểm thử và 4 cờ chạy 5 lần kiểm thử.

Tạo kiểm thử đơn vị (C và C++)

AOSP bao gồm các macro giá trị cờ cho các chương trình kiểm thử C và C++ được viết trong khung GoogleTest.

  1. Trong nguồn kiểm thử, hãy đưa vào các định nghĩa macro và thư viện do aconfig tạo:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. Trong nguồn kiểm thử, thay vì sử dụng macro TESTTESTF cho các trường hợp kiểm thử, hãy sử dụng 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");
    }
    

    Trong trường hợp:

    • Sử dụng macro TEST_WITH_FLAGSTEST_F_WITH_FLAGS thay vì macro TESTTEST_F.
    • REQUIRES_FLAGS_ENABLED xác định một tập hợp cờ phát hành tính năng phải đáp ứng điều kiện bật. Bạn có thể viết các cờ này trong macro ACONFIG_FLAG hoặc LEGACY_FLAG.
    • REQUIRES_FLAGS_DISABLED xác định một tập hợp cờ tính năng phải đáp ứng điều kiện tắt. Bạn có thể viết các cờ này trong macro ACONFIG_FLAG hoặc LEGACY_FLAG.
    • ACONFIG_FLAG (TEST_NS, readwrite_enabled_flag) là một macro dùng cho các cờ được xác định trong tệp aconfig. Macro này chấp nhận một không gian tên (TEST_NS) và tên cờ (readwrite_enabled_flag).
    • LEGACY_FLAG(aconfig_flags.cts, TEST_NS, readwrite_disabled_flag) là một macro dùng cho các cờ được đặt trong cấu hình thiết bị theo mặc định.
  3. Trong tệp bản dựng Android.bp, hãy thêm các thư viện do aconfig tạo và các thư viện macro có liên quan làm phần phụ thuộc kiểm thử:

    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. Chạy các chương trình kiểm thử cục bộ bằng lệnh sau:

    atest FlagMacrosTests
    

    Nếu cờ my_namespace.android.myflag.tests.my_flag bị tắt, kết quả kiểm thử sẽ là:

    [1/2] MyTest#test1: IGNORED (0ms)
    [2/2] MyTestF#test2: PASSED (0ms)
    

    Nếu cờ my_namespace.android.myflag.tests.my_flag được bật, kết quả kiểm thử sẽ là:

    [1/2] MyTest#test1: PASSED (0ms)
    [2/2] MyTestF#test2: IGNORED (0ms)
    

Tạo kiểm thử toàn diện hoặc kiểm thử đơn vị mà giá trị cờ không thay đổi

Đối với các trường hợp kiểm thử mà bạn không thể ghi đè cờ và chỉ có thể lọc các kiểm thử nếu các kiểm thử đó dựa trên trạng thái cờ hiện tại, hãy sử dụng quy tắc CheckFlagsRule với chú thích RequiresFlagsEnabledRequiresFlagsDisabled.

Các bước sau đây hướng dẫn bạn cách tạo và chạy kiểm thử toàn diện hoặc kiểm thử đơn vị mà không thể ghi đè các giá trị cờ:

  1. Trong mã kiểm thử, hãy sử dụng CheckFlagsRule để áp dụng tính năng lọc kiểm thử. Ngoài ra, hãy sử dụng chú thích Java RequiresFlagsEnabledRequiredFlagsDisabled để chỉ định các yêu cầu về cờ cho kiểm thử của bạn.

    Kiểm thử phía thiết bị sử dụng lớp 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() {}
    }
    

    Kiểm thử phía máy chủ sử dụng lớp 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. Thêm jflag-unit và các thư viện do aconfig tạo vào phần static_libs của tệp bản dựng để kiểm thử:

    android_test {
        name: "FlagAnnotationTests",
        srcs: ["*.java"],
        static_libs: [
            "androidx.test.rules",
            "my_aconfig_lib",
            "flag-junit",
            "platform-test-annotations",
        ],
        test_suites: ["general-tests"],
    }
    
  3. Sử dụng lệnh sau để chạy kiểm thử cục bộ:

    atest FlagAnnotationTests
    

    Nếu cờ Flags.FLAG_FLAG_NAME_1 bị tắt, kết quả kiểm thử sẽ là:

    [1/2] com.cts.flags.FlagAnnotationTest#test1: ASSUMPTION_FAILED (10ms)
    [2/2] com.cts.flags.FlagAnnotationTest#test2: PASSED (2ms)
    

    Nếu không, kết quả kiểm thử sẽ là:

    [1/2] com.cts.flags.FlagAnnotationTest#test1: PASSED (2ms)
    [2/2] com.cts.flags.FlagAnnotationTest#test2: ASSUMPTION_FAILED (10ms)
    

Giá trị mặc định của thiết bị

SetFlagsRule đã khởi chạy sử dụng các giá trị cờ từ thiết bị. Nếu giá trị cờ trên thiết bị không bị ghi đè, chẳng hạn như với adb, thì giá trị mặc định sẽ giống với cấu hình phát hành của bản dựng. Nếu giá trị trên thiết bị đã bị ghi đè, thì SetFlagsRule sẽ sử dụng giá trị ghi đè làm giá trị mặc định.

Nếu cùng một chương trình kiểm thử được thực thi theo nhiều cấu hình phát hành, thì giá trị của các cờ không được đặt rõ ràng bằng SetFlagsRule có thể thay đổi.

Sau mỗi lần kiểm thử, SetFlagsRule sẽ khôi phục thực thể FeatureFlags trong Flags về FeatureFlagsImpl ban đầu để không gây ra tác dụng phụ cho các phương thức và lớp kiểm thử khác.