تتوفّر الآن الإصدارات 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 القياسية، كما يوفّر تحكّمًا صريحًا في ترتيب تنفيذ الروتينات الفرعية.
عمليات ربط واجهة برمجة التطبيقات
عند الترقية إلى الإصدار 2 من واجهات برمجة التطبيقات، يمكنك بشكل عام استخدام البحث والاستبدال لتعديل عمليات استيراد الحِزم واعتماد التغييرات الجديدة في أداة الإرسال.
بدلاً من ذلك، يمكنك أن تطلب من Gemini نقل البيانات إلى الإصدار 2 من واجهات برمجة التطبيقات الخاصة باختبار Compose باستخدام الطلب التالي:
الطلب الموجَّه إلى الذكاء الاصطناعي
نقل البيانات من واجهات برمجة التطبيقات الخاصة بالاختبار في الإصدار 1 إلى واجهات برمجة التطبيقات الخاصة بالاختبار في الإصدار 2
سيستخدم هذا الطلب هذا الدليل للانتقال إلى الإصدار 2 من واجهات برمجة التطبيقات الخاصة بالاختبار.
Migrate to Compose testing v2 APIs using the official
migration guide.استخدِم الجدول التالي لربط واجهات برمجة التطبيقات v1 التي تم إيقافها نهائيًا بالبدائل المتوفّرة في الإصدار 2:
متوقّف نهائيًا (الإصدار 1) |
الاستبدال (الإصدار 2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
التوافق مع الأنظمة القديمة والاستثناءات
تم الآن إيقاف الإصدار 1 من واجهات برمجة التطبيقات نهائيًا، ولكن سيستمر استخدام UnconfinedTestDispatcher للحفاظ على السلوك الحالي وتجنُّب إجراء تغييرات غير متوافقة.
في ما يلي الاستثناء الوحيد الذي تم فيه تغيير السلوك التلقائي:
تم تغيير أداة إرسال الاختبار التلقائية المستخدَمة لتشغيل التركيب في الفئة AndroidComposeUiTestEnvironment من UnconfinedTestDispatcher إلى StandardTestDispatcher. يؤثّر ذلك في الحالات التي تنشئ فيها مثيلاً باستخدام الدالة الإنشائية أو الفئة الفرعية AndroidComposeUiTestEnvironment، وتستدعي تلك الدالة الإنشائية.
التغيير الرئيسي: التأثير في تنفيذ الروتين الفرعي
يكمن الاختلاف الأساسي بين الإصدار 1 والإصدار 2 من واجهات برمجة التطبيقات في طريقة إرسال الروتينات الفرعية:
- الإصدار 1 من واجهات برمجة التطبيقات (
UnconfinedTestDispatcher): عند تشغيل روتين فرعي، كان يتم تنفيذه على الفور في سلسلة المحادثات الحالية، وغالبًا ما كان ينتهي قبل تشغيل السطر التالي من رمز الاختبار. وعلى عكس سلوك الإنتاج، يمكن أن يؤدي هذا التنفيذ الفوري إلى إخفاء المشاكل الحقيقية المتعلقة بالتوقيت أو حالات التزامن التي قد تحدث في تطبيق مباشر. - الإصدار الثاني من واجهات برمجة التطبيقات (
StandardTestDispatcher): عند تشغيل كوروتين، يتم وضعه في قائمة الانتظار ولا يتم تنفيذه إلا بعد أن يقدّم الاختبار الساعة الافتراضية بشكل صريح. تتعامل واجهات برمجة التطبيقات القياسية لاختبار Compose (مثلwaitForIdle()) مع عملية المزامنة هذه، لذا من المفترض أن تواصل معظم الاختبارات التي تعتمد على واجهات برمجة التطبيقات القياسية هذه عملها بدون أي تغييرات.
الأعطال الشائعة وكيفية حلّها
إذا تعذّرت اختباراتك بعد الترقية إلى الإصدار 2، من المحتمل أن تظهر النمط التالي:
- تعذُّر: تبدأ مهمة (على سبيل المثال، تحميل ViewModel للبيانات)، ولكن يتعذّر التأكيد على الفور لأنّ البيانات لا تزال في حالة "جارٍ التحميل".
- السبب: باستخدام الإصدار 2 من واجهات برمجة التطبيقات، يتم وضع الروتينات الفرعية في قائمة الانتظار بدلاً من تنفيذها على الفور. تمت إضافة المهمة إلى قائمة الانتظار ولكن لم يتم تنفيذها فعليًا قبل التحقّق من النتيجة.
- الحلّ: تقديم الوقت بشكل صريح. يجب أن تخبر أداة الإرسال الإصدار 2 صراحةً بموعد تنفيذ العمل.
الطريقة السابقة
في الإصدار 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 إلى جانب واجهة برمجة تطبيقات Kotlin runTest،
ننصحك بشدة بالتبديل إلى 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() } }