تتوفّر الآن الإصدارات 2 من واجهات برمجة التطبيقات الخاصة بالاختبار في Compose (createComposeRule وcreateAndroidComposeRule وrunComposeUiTest وrunAndroidComposeUiTest وما إلى ذلك) لتحسين التحكّم في تنفيذ الروتينات الفرعية. لا يكرّر هذا التعديل مساحة واجهة برمجة التطبيقات بأكملها،
بل تم تعديل واجهات برمجة التطبيقات التي تنشئ بيئة الاختبار فقط.
تم إيقاف الإصدار 1 من واجهات برمجة التطبيقات نهائيًا، وننصحك بشدة بنقل البيانات إلى واجهات برمجة التطبيقات الجديدة. تضمن عملية النقل توافق اختباراتك مع السلوك العادي للروتينات الفرعية وتتجنّب مشاكل التوافق المستقبلية. للاطّلاع على قائمة بواجهات برمجة التطبيقات v1 التي تم إيقافها نهائيًا، يُرجى الرجوع إلى عمليات ربط واجهات برمجة التطبيقات.
يتم تضمين هذه التغييرات في
androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ وandroidx.compose.ui:ui-test:1.11.0-alpha03+.
بينما كانت واجهات برمجة التطبيقات للإصدار 1 تعتمد على UnconfinedTestDispatcher، تستخدم واجهات برمجة التطبيقات للإصدار 2 StandardTestDispatcher تلقائيًا للتركيب قيد التشغيل. يؤدي هذا التغيير إلى
مواءمة سلوك اختبار Compose مع واجهات برمجة التطبيقات runTest القياسية، كما يتيح
التحكّم بشكل صريح في ترتيب تنفيذ الروتينات الفرعية.
ضبط بيئة الاختبار
تستخدِم واجهات برمجة التطبيقات Compose test v2 ComposeUiTestConfig لتخصيص بيئة الاختبار. تقبل واجهات برمجة التطبيقات التي تنشئ دوال إعداد للاختبارات، مثل createComposeRule وrunComposeUiTest وغيرها من واجهات برمجة التطبيقات ذات الصلة، القيمة ComposeUiTestConfig. يجمع عنصر الإعداد هذا واجهات برمجة التطبيقات ذات الصلة بالبيئة، مثل effectContext وrunTestContext وtestTimeout، في عنصر واحد.
يدير نموذج الإعدادات أيضًا inputMode. تفرض واجهات برمجة التطبيقات Compose Test v2 استخدام InputMode.Touch تلقائيًا في بداية كل اختبار لضمان تحديد النتائج مسبقًا ومنع تسريب حالة وضع الإدخال بين الاختبارات.
تشكّل ComposeUiTestConfig جزءًا من واجهات برمجة التطبيقات Compose Test v2 التي تستخدم StandardTestDispatcher تلقائيًا. إذا كانت اختباراتك تستخدم الإصدار 1 من واجهات برمجة التطبيقات، اطّلِع على نقل البيانات إلى الإصدار 2 من واجهات برمجة التطبيقات الخاصة بالاختبار قبل استخدام ComposeUiTestConfig.
نقل البيانات إلى ComposeUiTestConfig
تم إيقاف العديد من عمليات التحميل الزائد التي تقبل مَعلمات إعداد فردية، مثل effectContext أو runTestContext أو testTimeout، ضمن عمليات التحميل الزائد لإنشاء دوال الإعداد في الاختبارات. عدِّل اختباراتك لاستخدام
ComposeUiTestConfig بدلاً من ذلك، كما هو موضّح في المثال التالي:
val testConfig = ComposeUiTestConfig( effectContext = EmptyCoroutineContext, runTestContext = EmptyCoroutineContext, testTimeout = 30.seconds ) @get:Rule val rule = createComposeRule(config = testConfig) // OR runComposeUiTest(config = testConfig) {}
وضع الإدخال التلقائي
قد تفشل الاختبارات أثناء عملية النقل إذا كانت تعتمد على أوضاع الإدخال غير المستندة إلى اللمس
التي تم ضبطها من خلال واجهات برمجة التطبيقات الخاصة بأدوات القياس قبل بدء الاختبار. ضمن دوال الإعداد الخاصة بالاختبارات، يفرض النظام InputMode.Touch تلقائيًا في بداية كل اختبار لزيادة التحديد ومنع تسرُّب الحالة، مع تجاهل حالة الجهاز المحيطة والإعداد المسبق للاختبار.
لحلّ هذه المشكلة، حدِّد وضع الإدخال المطلوب في ComposeUiTestConfig:
class FocusTest { @get:Rule val rule = createComposeRule( config = ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) @Test fun testFocus() {} }
لضبط وضع الإدخال لحالات الاختبار الفردية بدلاً من فئة الاختبار بأكملها، مرِّر ComposeUiTestConfig إلى runComposeUiTest:
class FocusTest { @Test fun testTouchMode() = runComposeUiTest { // Runs with the default InputMode.Touch } @Test fun testKeyboardMode() = runComposeUiTest( ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) { // Runs with InputMode.Keyboard } }
للاطّلاع على المشاكل الأخرى المتعلقة بعملية النقل وحلولها، يُرجى الانتقال إلى الأعطال الشائعة وكيفية حلّها.
نقل البيانات إلى الإصدار 2 من واجهات برمجة التطبيقات الخاصة بالاختبار
عند الترقية إلى الإصدار 2 من واجهات برمجة التطبيقات، يمكنك بشكل عام استخدام البحث والاستبدال لتعديل عمليات استيراد الحِزم واعتماد التغييرات الجديدة في أداة الإرسال.
بدلاً من ذلك، يمكنك أن تطلب من Gemini نقل البيانات إلى الإصدار 2 من واجهات برمجة التطبيقات الخاصة باختبار Compose باستخدام الطلب التالي:
طلب موجَّه إلى الذكاء الاصطناعي
نقل البيانات من واجهات برمجة التطبيقات الخاصة بالاختبار في الإصدار 1 إلى واجهات برمجة التطبيقات الخاصة بالاختبار في الإصدار 2
سيستخدم هذا الطلب هذا الدليل لنقل البيانات إلى الإصدار 2 من واجهات برمجة التطبيقات الخاصة بالاختبار.
Migrate to Compose testing v2 APIs using the official
migration guide.استخدِم الجدول التالي لربط واجهات برمجة التطبيقات v1 التي تم إيقافها نهائيًا بواجهات برمجة التطبيقات v2 البديلة:
متوقّف نهائيًا (الإصدار 1) |
الاستبدال (الإصدار 2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
التوافق مع الأنظمة القديمة والاستثناءات
تم الآن إيقاف الإصدار 1 من واجهات برمجة التطبيقات نهائيًا، ولكن سيستمر استخدام UnconfinedTestDispatcher للحفاظ على السلوك الحالي وتجنُّب أي تغييرات غير متوافقة.
في ما يلي الاستثناء الوحيد الذي تم فيه تغيير السلوك التلقائي:
تم تغيير أداة إرسال الاختبار التلقائية المستخدَمة لتشغيل التركيب في الفئة AndroidComposeUiTestEnvironment من UnconfinedTestDispatcher إلى StandardTestDispatcher. يؤثّر ذلك في الحالات التي تنشئ فيها مثيلاً باستخدام الدالة الإنشائية أو الفئة الفرعية AndroidComposeUiTestEnvironment، وتستدعي تلك الدالة الإنشائية.
التغيير الرئيسي: التأثير في تنفيذ الروتين الفرعي
يتمثّل الاختلاف الأساسي بين الإصدار 1 والإصدار 2 من واجهات برمجة التطبيقات في طريقة إرسال الروتينات الفرعية:
- واجهات برمجة التطبيقات v1 (
UnconfinedTestDispatcher): عند تشغيل روتين فرعي، كان يتم تنفيذه على الفور في سلسلة المحادثات الحالية، وغالبًا ما كان ينتهي قبل تشغيل السطر التالي من رمز الاختبار. وعلى عكس سلوك الإنتاج، يمكن أن يؤدي هذا التنفيذ الفوري إلى إخفاء المشاكل الحقيقية المتعلقة بالتوقيت أو حالات التزامن التي قد تحدث في تطبيق مباشر. - الإصدار 2 من واجهات برمجة التطبيقات (
StandardTestDispatcher): عند تشغيل كوروتين، يتم وضعه في قائمة الانتظار ولا يتم تنفيذه إلا بعد أن يقدّم الاختبار الساعة الافتراضية بشكل صريح. تتعامل واجهات برمجة التطبيقات القياسية لاختبار Compose (مثلwaitForIdle()) مع عملية المزامنة هذه، لذا من المفترض أن تواصل معظم الاختبارات التي تعتمد على واجهات برمجة التطبيقات القياسية هذه عملها بدون أي تغييرات.
الأعطال الشائعة وكيفية حلّها
إذا تعذّرت اختباراتك بعد الترقية إلى الإصدار 2، من المحتمل أن تظهر النتيجة التالية:
- تعذُّر: يمكنك تشغيل مهمة (على سبيل المثال، تحميل ViewModel للبيانات)، ولكن يتعذّر تأكيدك على الفور لأنّ البيانات لا تزال في حالة "جارٍ التحميل".
- السبب: باستخدام الإصدار 2 من واجهات برمجة التطبيقات، يتم وضع الروتينات الفرعية في قائمة الانتظار بدلاً من تنفيذها على الفور. تمت إضافة المهمة إلى قائمة الانتظار ولكن لم يتم تنفيذها مطلقًا قبل التحقّق من النتيجة.
- الحلّ: يمكنك تقديم الوقت بشكل صريح. يجب أن تخبر أداة الإرسال v2 صراحةً بموعد تنفيذ العمل.
الطريقة السابقة
في الإصدار 1، تم تشغيل المهمة وإكمالها على الفور. في الإصدار 2، ستتعذّر تنفيذ الرمز التالي لأنّ loadData() لم يتم تنفيذه بعد.
// In v1, this launched and finished immediately.
viewModel.loadData()
// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)
الطريقة المُقترحة
استخدِم waitForIdle أو runOnIdle لتنفيذ المهام المدرَجة في قائمة الانتظار قبل التأكيد.
الخيار 1: يؤدي استخدام waitForIdle إلى تقديم الوقت حتى يصبح واجهة المستخدم غير نشطة،
ما يؤكّد أنّ الروتين الفرعي قد تم تنفيذه.
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
الخيار 2: يؤدي استخدام runOnIdle إلى تنفيذ مجموعة الرموز في سلسلة التعليمات البرمجية لواجهة المستخدم بعد أن تصبح واجهة المستخدم غير نشطة.
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
المزامنة اليدوية
في السيناريوهات التي تتضمّن المزامنة اليدوية، مثل الحالات التي يكون فيها التقدّم التلقائي غير مفعّل، لن يؤدي تشغيل روتين فرعي إلى تنفيذ فوري لأنّ ساعة الاختبار تكون متوقفة مؤقتًا. لتنفيذ الكوروتينات في الصفّ بدون تقديم الساعة الافتراضية، استخدِم واجهة برمجة التطبيقات runCurrent(). يتم تنفيذ المهام
المجدوَلة للوقت الافتراضي الحالي.
composeTestRule.mainClock.scheduler.runCurrent()
على عكس waitForIdle()، الذي يقدّم وقت الاختبار إلى أن تستقر واجهة المستخدم، ينفّذ runCurrent() المهام المعلّقة مع الحفاظ على الوقت الافتراضي الحالي. يتيح هذا السلوك التحقّق من الحالات الوسيطة التي كان سيتم تخطّيها إذا تم تقديم الوقت إلى حالة عدم النشاط.
يتم عرض أداة جدولة الاختبار الأساسية المستخدَمة في بيئة الاختبار. يمكن استخدام أداة الجدولة هذه مع واجهة برمجة التطبيقات runTest في Kotlin لمزامنة ساعة الاختبار.
نقل البيانات إلى runComposeUiTest
إذا كنت تستخدم واجهات برمجة التطبيقات لاختبار Compose إلى جانب واجهة برمجة التطبيقات runTest في Kotlin، ننصحك بشدة بالتبديل إلى runComposeUiTest.
الطريقة السابقة
يؤدي استخدام createComposeRule مع runTest إلى إنشاء ساعتين منفصلتين، إحداهما لـ Compose والأخرى لنطاق روتين الاختبار المشترك. يمكن أن يجبرك هذا الإعداد على مزامنة أداة جدولة الاختبار يدويًا.
@get:Rule val composeTestRule = createComposeRule() @Test fun testWithCoroutines() { composeTestRule.setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } // NOT RECOMMENDED // Fails: runTest creates a new, separate scheduler. // Advancing time here does NOT advance the compose clock. // To fix this without migrating, you would need to share the scheduler // by passing 'composeTestRule.mainClock.scheduler' to runTest. runTest { composeTestRule.onNodeWithText("Loading...").assertIsDisplayed() advanceTimeBy(1000) composeTestRule.onNodeWithText("Done!").assertIsDisplayed() } }
الطريقة المُقترحة
تنفِّذ واجهة برمجة التطبيقات runComposeUiTest تلقائيًا مجموعة الاختبارات ضمن نطاقها runTest. تتم مزامنة ساعة الاختبار مع بيئة Compose، وبالتالي لن تحتاج إلى إدارة المجدول يدويًا.
@Test fun testWithCoroutines() = runComposeUiTest { setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } onNodeWithText("Loading...").assertIsDisplayed() mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */) onNodeWithText("Done!").assertIsDisplayed() } }