مع طرح علامات إطلاق الميزات، هناك سياسات اختبار جديدة يجب الالتزام بها:
- يجب أن تغطي اختباراتك السلوكيات المفعَّلة وغير المفعَّلة للعلامة.
- يجب استخدام الآليات الرسمية لضبط قيم العلامات أثناء الاختبار.
- يجب ألا تتجاهل اختبارات 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.
إنشاء وحدات اختبار معلَمة استنادًا إلى حالات العلامات
لإنشاء وحدات اختبار معلَمة استنادًا إلى حالات العلامات:
أدرِج
FeatureFlagTargetPreparerفي ملف إعدادات وحدة الاختبارAndroidTest.xml:<target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >حدِّد خيارات قيم العلامات في قسم
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.
لكتابة اختبارات الوحدة الآلية في قاعدة رموز برمجية كبيرة تحتوي على عدد كبير من العلامات، اتّبِع الخطوات التالية:
- استخدِم الفئة
SetFlagsRuleمع التعليمتَين التوضيحيتَين@EnableFlagsو@DisableFlagsلاختبار جميع فروع الرموز البرمجية. - استخدِم الطريقة
SetFlagsRule.ClassRuleلتجنُّب أخطاء الاختبار الشائعة. - استخدِم
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هيtrueFLAG_FOOهيtrueوFLAG_BARهيfalseFLAG_FOOهيfalseوFLAG_BARهيtrueFLAG_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.
في مصدر الاختبار، أدرِج تعريفات الماكرو والمكتبات التي تم إنشاؤها باستخدام aconfig:
#include <flag_macros.h> #include "android_cts_flags.h"في مصدر الاختبار، بدلاً من استخدام
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)هي وحدة ماكرو تُستخدم للعلامات التي يتم ضبطها في إعدادات الجهاز تلقائيًا.
- يتم استخدام وحدتَي الماكرو
في ملف الإصدار
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"], ... }شغِّل الاختبارات محليًا باستخدام هذا الأمر:
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.
توضّح لك الخطوات التالية كيفية إنشاء اختبار شامل أو اختبار وحدة وتشغيلهما عندما لا يمكن إلغاء قيم العلامات:
في رمز الاختبار، استخدِم
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() {} }أضِف
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"], }استخدِم الأمر التالي لتشغيل الاختبار محليًا:
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 الأصلي، حتى لا يكون له آثار جانبية على
طرق وفئات الاختبار الأخرى.