مع طرح علامات إطلاق الميزات، أصبحت هناك سياسات اختبار جديدة يجب الالتزام بها:
- يجب أن تغطّي اختباراتك السلوكيات المفعَّلة وغير المفعَّلة للعَلم.
- يجب استخدام الآليات الرسمية لضبط قيم العلامات أثناء الاختبار.
- يجب ألا تتجاوز اختبارات 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.
إنشاء وحدات اختبار ذات معلَمات استنادًا إلى حالات العلامات
لإنشاء وحدات اختبار ذات معلَمات استنادًا إلى حالات العلامات، اتّبِع الخطوات التالية:
أدرِج
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-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هو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 عملية تشغيل لكل اختبار. على سبيل المثال، يشغّل أحد الخيارَين اختبارَين أو تشغّل أربعة خيارات 16 اختبارًا.ينفّذ
FlagsParameterization.progressionOf(String...)عمليات تشغيل n+1 لكل اختبار. على سبيل المثال، يشغّل أحد الأعلام اختبارَين، وتشغّل أربعة أعلام 5 أضعاف عدد الأعلام.
إنشاء اختبارات الوحدة (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)هي وحدة ماكرو تُستخدَم مع العلامات المحدّدة في ملفات الإعداد. تقبل وحدة الماكرو هذه مساحة اسم (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لتطبيق فلترة الاختبار. استخدِم أيضًا تعليقات 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() {} }أضِف
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 الأصلي، وذلك حتى لا يكون له آثار جانبية على طرق وفئات الاختبار الأخرى.