اختبار الرمز ضمن علامات إطلاق الميزات

مع طرح علامات إطلاق الميزات، هناك سياسات اختبار جديدة يجب الالتزام بها:

  • يجب أن تغطي اختباراتك السلوكيات المفعَّلة وغير المفعَّلة للعلامة.
  • يجب استخدام الآليات الرسمية لضبط قيم العلامات أثناء الاختبار.
  • يجب ألا تتجاهل اختبارات xTS قيم العلامات في الاختبارات.

يقدّم القسم التالي الآليات الرسمية التي يجب استخدامها للالتزام بهذه السياسات.

اختبار الرمز الذي تم وضع علامة عليه

سيناريو الاختبار الآلية المستخدَمة
الاختبار المحلي عندما تتغيّر قيم العلامات بشكل متكرّر جسر تصحيح أخطاء Android كما هو موضّح في تغيير قيمة علامة في وقت التشغيل
الاختبار المحلي عندما لا تتغيّر قيم العلامات بشكل متكرّر ملف قيم العلامات كما هو موضّح في ضبط قيم علامة إطلاق الميزة
الاختبار الشامل عندما تتغيّر قيم العلامات FeatureFlagTargetPreparer كما هو موضّح في إنشاء اختبارات شاملة
اختبار الوحدة عندما تتغيّر قيم العلامات SetFlagsRule مع @EnableFlags و@DisableFlags كما هو موضّح في إنشاء اختبارات الوحدة (Java وKotlin) أو إنشاء اختبارات الوحدة (C وC++)
الاختبار الشامل أو اختبار الوحدة عندما لا يمكن تغيير قيم العلامات CheckFlagsRule كما هو موضّح في إنشاء اختبارات شاملة أو اختبارات الوحدة عندما لا تتغيّر قيم العلامات

إنشاء اختبارات شاملة

يوفّر مشروع 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.

لكتابة اختبارات الوحدة الآلية في قاعدة رموز برمجية كبيرة تحتوي على عدد كبير من العلامات، اتّبِع الخطوات التالية:

  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 هي تعليمة توضيحية تُستخدم لإضافة الاعتمادية على 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، يتعذّر إجراء test_flag_foo_turned_on قبل تشغيله عندما يقرأ أداة إنشاء DemoClass العلامة FLAG_FLAG_FOO.

إذا كانت فئتك بأكملها بحاجة إلى تفعيل علامة، انقل التعليمة التوضيحية @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_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 هي false وFLAG_BAR هي false

بدلاً من تغيير قيم العلامات مباشرةً، تُعدِّل التعليمتان التوضيحيتان @DisableFlags و@EnableFlags قيم العلامات استنادًا إلى شروط المعلمة. على سبيل المثال، لا يتم تشغيل legacyBarLogic إلا عندما تكون FLAG_BAR غير مفعَّلة، ويحدث ذلك في اثنتَين من تركيبات العلامات الأربع. يتم تخطّي legacyBarLogic للتركيبتَين الأخريَين.

هناك طريقتان لإنشاء المعلمات لعلاماتك:

  • FlagsParameterization.allCombinationsOf(String...) تنفّذ 2^n من عمليات تشغيل كل اختبار. على سبيل المثال، تشغّل علامة واحدة اختبارات 2x أو تشغّل أربع علامات اختبارات 16x.

  • FlagsParameterization.progressionOf(String...) تنفّذ n+1 من عمليات تشغيل كل اختبار. على سبيل المثال، تشغّل علامة واحدة اختبارات 2x وتشغّل أربع علامات اختبارات 5x.

إنشاء اختبارات الوحدة (C وC++)

يتضمّن مشروع AOSP وحدات ماكرو لقيم العلامات لاختبارات C وC++ المكتوبة في إطار عمل GoogleTest.

  1. في مصدر الاختبار، أدرِج تعريفات الماكرو والمكتبات التي تم إنشاؤها باستخدام aconfig:

    #include <flag_macros.h>
    #include "android_cts_flags.h"
    
  2. في مصدر الاختبار، بدلاً من استخدام TEST وTESTF لوحدات الماكرو لحالات الاختبار، استخدِم TEST_WITH_FLAGS وTEST_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");
    }
    

    المكان:

    • يتم استخدام وحدتَي الماكرو 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) هي وحدة ماكرو تُستخدم للعلامات المحدّدة في ملفات aconfig. تقبل هذه الوحدة مساحة اسم (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)
    

إنشاء اختبارات شاملة أو اختبارات الوحدة عندما لا تتغيّر قيم العلامات

بالنسبة إلى حالات الاختبار التي لا يمكنك فيها إلغاء العلامات ولا يمكنك فلترة الاختبارات إلا إذا كانت تستند إلى حالة العلامة الحالية، استخدِم القاعدة CheckFlagsRule مع التعليمتَين التوضيحيتَين RequiresFlagsEnabled وRequiresFlagsDisabled.

توضّح لك الخطوات التالية كيفية إنشاء اختبار شامل أو اختبار وحدة وتشغيلهما عندما لا يمكن إلغاء قيم العلامات:

  1. في رمز الاختبار، استخدِم CheckFlagsRule لتطبيق فلترة الاختبار. استخدِم أيضًا التعليمتَين التوضيحيتَين RequiresFlagsEnabled وRequiredFlagsDisabled في Java لتحديد متطلبات العلامة لاختبارك.

    يستخدم الاختبار على جانب الجهاز الفئة 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 الأصلي، حتى لا يكون له آثار جانبية على طرق وفئات الاختبار الأخرى.