ضبط بيئة الاختبار باستخدام واجهات برمجة التطبيقات الخاصة بالاختبار في الإصدار 2

تتوفّر الآن الإصدارات الثانية من واجهات برمجة التطبيقات لاختبار Compose (createComposeRule وcreateAndroidComposeRule وrunComposeUiTest وrunAndroidComposeUiTest وما إلى ذلك) لتحسين التحكّم في تنفيذ الكوروتين. لا يكرّر هذا التعديل واجهة برمجة التطبيقات بالكامل، بل تم تعديل واجهات برمجة التطبيقات التي تنشئ بيئة الاختبار فقط.

تم إيقاف الإصدار 1 من واجهات برمجة التطبيقات نهائيًا، وننصح بشدة بنقل البيانات إلى واجهات برمجة التطبيقات الجديدة. تساعد عملية نقل البيانات في التحقّق من توافق اختباراتك مع سلوك الكوروتين العادي وتجنُّب مشاكل التوافق المستقبلية. للحصول على قائمة بواجهات برمجة التطبيقات المتوقّفة نهائيًا في الإصدار 1، يُرجى الاطّلاع على عمليات ربط واجهات برمجة التطبيقات.

في حين أنّ الإصدار 1 من واجهات برمجة التطبيقات كان يعتمد على UnconfinedTestDispatcher، يستخدم الإصدار 2 من واجهات برمجة التطبيقات StandardTestDispatcher تلقائيًا لعملية الدمج الجارية. يؤدي هذا التغيير إلى مواءمة سلوك اختبار Compose مع واجهات برمجة تطبيقات runTest العادية، كما يوفّر تحكّمًا صريحًا في ترتيب تنفيذ الكوروتين.

ضبط بيئة الاختبار

تستخدم واجهات برمجة التطبيقات Compose test v2‏ ComposeUiTestConfig لتخصيص بيئة الاختبار. واجهات برمجة التطبيقات التي تنشئ دوال إعداد للاختبارات، مثل createComposeRule وrunComposeUiTest وواجهات برمجة التطبيقات الأخرى ذات الصلة، تقبل ComposeUiTestConfig. يجمع عنصر الإعداد هذا واجهات برمجة التطبيقات ذات الصلة بالبيئة، مثل effectContext وrunTestContext وtestTimeout، في عنصر واحد.

يدير نموذج الإعدادات أيضًا inputMode. تفرض واجهات برمجة التطبيقات Compose test v2 استخدام InputMode.Touch تلقائيًا في بداية كل اختبار لضمان الحتمية ومنع تسرُّب حالة وضع الإدخال بين الاختبارات.

‫ComposeUiTestConfig هي جزء من الإصدار 2 من واجهات برمجة التطبيقات لاختبار Compose، والتي تستخدم StandardTestDispatcher تلقائيًا. إذا كانت اختباراتك تستخدم الإصدار 1 من واجهات برمجة التطبيقات، يُرجى الاطّلاع على الانتقال إلى الإصدار 2 من واجهات برمجة التطبيقات للاختبار قبل استخدام ComposeUiTestConfig.

نقل البيانات إلى ComposeUiTestConfig

ضمن التحميلات الزائدة لإنشاء دوال الإعداد في الاختبارات، تم إيقاف نهائيًا العديد من التحميلات الزائدة التي تقبل مَعلمات الإعداد الفردية، مثل effectContext أو runTestContext أو testTimeout. يُرجى تعديل اختباراتك لاستخدام ComposeUiTestConfig بدلاً من ذلك، كما هو موضّح في المثال التالي:

وضع الإدخال التلقائي

قد تفشل الاختبارات أثناء عملية النقل إذا كانت تعتمد على أوضاع الإدخال غير اللمسية التي تم ضبطها من خلال واجهات برمجة التطبيقات الخاصة بالأدوات قبل بدء الاختبار. ضمن دوال الإعداد للاختبارات، يفرض النظام 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 من واجهات برمجة التطبيقات الخاصة بالاختبار

عند الترقية إلى الإصدار الثاني من واجهات برمجة التطبيقات، يمكنك بشكل عام استخدام ميزة البحث والاستبدال لتعديل عمليات استيراد الحِزم واعتماد التغييرات الجديدة في المُرسِل.

بدلاً من ذلك، اسأل Gemini عن إجراء نقل إلى الإصدار 2 من واجهات برمجة التطبيقات الخاصة باختبار Compose باستخدام الطلب التالي:

نقل البيانات من الإصدار 1 إلى الإصدار 2 من واجهات برمجة التطبيقات الخاصة بالاختبار

سيستخدم هذا الطلب هذا الدليل لنقل البيانات إلى الإصدار 2 من واجهات برمجة التطبيقات للاختبار.

Migrate to Compose testing v2 APIs using the official
migration guide.

استخدام الطلبات المستندة إلى الذكاء الاصطناعي

تم تصميم طلبات الذكاء الاصطناعي لاستخدامها في "Gemini في استوديو Android".

مزيد من المعلومات حول Gemini في Studio هنا: https://developer.android.com/studio/gemini/overview

استخدِم الجدول التالي لربط واجهات برمجة التطبيقات المتوقفة نهائيًا في الإصدار 1 ببدائلها في الإصدار 2:

متوقّف نهائيًا (الإصدار 1)

الاستبدال (الإصدار 2)

androidx.compose.ui.test.junit4.createComposeRule

androidx.compose.ui.test.junit4.v2.createComposeRule

androidx.compose.ui.test.junit4.createAndroidComposeRule

androidx.compose.ui.test.junit4.v2.createAndroidComposeRule

androidx.compose.ui.test.junit4.createEmptyComposeRule

androidx.compose.ui.test.junit4.v2.createEmptyComposeRule

androidx.compose.ui.test.junit4.AndroidComposeTestRule

androidx.compose.ui.test.junit4.v2.AndroidComposeTestRule

androidx.compose.ui.test.runComposeUiTest

androidx.compose.ui.test.v2.runComposeUiTest

androidx.compose.ui.test.runAndroidComposeUiTest

androidx.compose.ui.test.v2.runAndroidComposeUiTest

androidx.compose.ui.test.runEmptyComposeUiTest

androidx.compose.ui.test.v2.runEmptyComposeUiTest

androidx.compose.ui.test.AndroidComposeUiTestEnvironment

androidx.compose.ui.test.v2.AndroidComposeUiTestEnvironment

التوافق مع الأنظمة القديمة والاستثناءات

تم إيقاف الإصدارات الحالية من واجهات برمجة التطبيقات v1 نهائيًا، ولكن يمكنك مواصلة استخدام UnconfinedTestDispatcher للحفاظ على السلوك الحالي ومنع حدوث تغييرات قد تؤدي إلى عطل.

في ما يلي الاستثناء الوحيد الذي تم فيه تغيير الإعداد التلقائي:

تم تبديل المُرسِل التلقائي للاختبار المستخدَم لتشغيل التركيب في فئة AndroidComposeUiTestEnvironment من UnconfinedTestDispatcher إلى StandardTestDispatcher. يؤثر ذلك في الحالات التي تنشئ فيها مثيلاً باستخدام الدالة الإنشائية أو الفئة الفرعية AndroidComposeUiTestEnvironment، وتستدعي تلك الدالة الإنشائية.

التغيير الرئيسي: التأثير في تنفيذ الكوروتين

يتمثّل الاختلاف الأساسي بين الإصدارَين الأول والثاني من واجهات برمجة التطبيقات في كيفية إرسال الكوروتين:

  • واجهات برمجة التطبيقات v1 (UnconfinedTestDispatcher): عند تشغيل دالة coroutine، يتم تنفيذها على الفور في سلسلة التعليمات الحالية، وغالبًا ما تنتهي قبل تشغيل السطر التالي من رمز الاختبار. على عكس سلوك الإنتاج، قد يؤدي هذا التنفيذ الفوري إلى إخفاء مشاكل التوقيت الحقيقية أو حالات السباق التي قد تحدث في تطبيق مباشر عن غير قصد.
  • واجهات برمجة التطبيقات من الإصدار 2 (StandardTestDispatcher): عند تشغيل دالة كوروتين، تتم إضافتها إلى قائمة الانتظار ولا يتم تنفيذها إلى أن يقدّم الاختبار الساعة الافتراضية بشكل صريح. تتعامل واجهات برمجة التطبيقات العادية لاختبار Compose (مثل waitForIdle()) مع عملية المزامنة هذه، لذا من المفترض أن تستمر معظم الاختبارات التي تعتمد على واجهات برمجة التطبيقات العادية هذه في العمل بدون أي تغييرات.

الأعطال الشائعة وكيفية حلّها

إذا تعذّر إجراء الاختبارات بعد الترقية إلى الإصدار 2، من المرجّح أن تظهر لك الأنماط التالية:

  • الفشل: تبدأ مهمة (على سبيل المثال، يحمّل ViewModel البيانات)، ولكن يفشل التأكيد على الفور لأنّ البيانات لا تزال في حالة "جارٍ التحميل".
  • السبب: في الإصدار 2 من واجهات برمجة التطبيقات، تتم إضافة الكوروتين إلى قائمة الانتظار بدلاً من تنفيذها على الفور. تمت إضافة المهمة إلى قائمة الانتظار ولكن لم يتم تشغيلها فعليًا قبل التحقق من النتيجة.
  • الحلّ: تقدُّم الوقت بشكل صريح. يجب أن تخبر المرسِل v2 صراحةً بموعد تنفيذ العمل.

الطريقة السابقة

في الإصدار الأول، تم تشغيل المهمة وانتهت على الفور. في الإصدار 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()
    }
}