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

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

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

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

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

سيناريو الاختبار آلية الاستخدام
الاختبار المحلي عند تغيُّر قيم العلامات بشكل متكرّر أداة Android Debug Bridge كما هو موضّح في تغيير قيمة علامة في وقت التشغيل
الاختبار المحلي عندما لا تتغيّر قيم العلامات كثيرًا ملف قيم العلامات كما هو موضّح في ضبط قيم علامات إطلاق الميزات
الاختبار الشامل الذي تتغير فيه قيم العلامات 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-annotated في الاختبار. تعود قيم العلامات إلى حالتها السابقة عند انتهاء 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 عملية تشغيل لكل اختبار. على سبيل المثال، يشغّل أحد الخيارَين اختبارَين أو تشغّل أربعة خيارات 16 اختبارًا.

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