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

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

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

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

اختبار الرمز الذي تم الإبلاغ عنه

سيناريو الاختبار الآلية المستخدَمة
الاختبار على الجهاز عندما تتغيّر قيم العلامة بشكل متكرّر كما هو موضّح في مقالة تغيير قيمة العلامة أثناء وقت التشغيل.
اختبار محلي عندما لا تتغيّر قيم العلامات بشكل متكرر وضع علامة على ملف القيم كما هو موضَّح في مقالة ضبط قيم علامات إطلاق الميزات
الاختبار الشامل الذي تتغيّر فيه قيم العلامة 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 هو تعليق توضيحي يُستخدَم لإضافة تبعية tag-JUnit للفئة SetFlagsRule.
  • SetFlagsRule هي فئة مساعدة يتم توفيرها لإلغاء قيم العلامات. للحصول على معلومات حول كيفية تحديد SetFlagsRule للقيم التلقائية، يُرجى الاطّلاع على القيم التلقائية للجهاز.
  • @EnableFlags هو تعليق توضيحي يقبل عددًا عشوائيًا من أسماء العلامات. عند إيقاف الإشارات، استخدِم @DisableFlags. يمكنك تطبيق هذه التعليقات التوضيحية على طريقة أو فئة.

اضبط قيم العلامة لعملية الاختبار بأكملها، بدءًا من SetFlagsRule، قبل أي @Before منهج إعداد مُشارَك فيه في الاختبار. تعود قيم العلامة إلى حالتها السابقة عند انتهاء SetFlagsRule، أي بعد أي طُرق إعداد مُشارَك فيها @After.

التأكّد من ضبط العلامات بشكل صحيح

كما ذكرنا سابقًا، يتم استخدام SetFlagsRule مع التعليق التوضيحي @Rule في JUnit، ما يعني أنّه لا يمكن أن تضمن SetFlagsRule ضبط علاماتك بشكل صحيح أثناء إنشاء 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 قبل تشغيل FLAG_FLAG_FOO بواسطة الدالة الإنشائية DemoClass.

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

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

تتوفّر طريقتان لإنشاء مَعلمات العلامات:

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

  • ينفذ FlagsParameterization.progressionOf(String...) تشغيل n+1 لكل اختبار. على سبيل المثال، يؤدي رمز واحد إلى تشغيل اختبارَين، ويؤدي أربعة رموز إلى تشغيل 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 ومكتبات وحدات الماكرو ذات الصلة كاعتمادية اختبارية:

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