اختبار الصور المتحركة

تتيح لك وظيفة Compose Offers ComposeTestRule كتابة اختبارات للصور المتحركة بطريقة حتمية مع التحكّم الكامل في ساعة الاختبار. يتيح لك ذلك التحقّق من قيم الرسوم المتحركة الوسيطة. بالإضافة إلى ذلك، يمكن أن يتم تشغيل الاختبار بشكل أسرع من المدة الفعلية للحركة.

تعرض ComposeTestRule ساعة الاختبار على النحو mainClock. يمكنك ضبط قيمة السمة autoAdvance على false للتحكّم في الساعة في رمز الاختبار. بعد بدء الحركة التي تريد اختبارها، يمكن تقديم الساعة باستخدام advanceTimeBy.

يُرجى العِلم أنّ advanceTimeBy لا ينقل الساعة بالضبط بالمدة المحدّدة. بل يتم تقريبه إلى أقرب مدة زمنية تكون مضاعفًا لمدة عرض اللقطة.

@get:Rule
val rule = createComposeRule()

@Test
fun testAnimationWithClock() {
    // Pause animations
    rule.mainClock.autoAdvance = false
    var enabled by mutableStateOf(false)
    rule.setContent {
        val color by animateColorAsState(
            targetValue = if (enabled) Color.Red else Color.Green,
            animationSpec = tween(durationMillis = 250)
        )
        Box(Modifier.size(64.dp).background(color))
    }

    // Initiate the animation.
    enabled = true

    // Let the animation proceed.
    rule.mainClock.advanceTimeBy(50L)

    // Compare the result with the image showing the expected result.
    // `assertAgainGolden` needs to be implemented in your code.
    rule.onRoot().captureToImage().assertAgainstGolden()
}

تحسين اختبارات الصور المتحركة

عند اختبار الرسوم المتحركة عالية الدقة، عليك غالبًا إيقاف التقدّم التلقائي والانتقال يدويًا بين اللقطات لتأكيد حالات واجهة المستخدم الوسيطة. بالنسبة إلى حلقات التكرار المحدّدة هذه، استخدِم طريقة runWithoutImplicitWait لتنفيذ تأكيداتك. تؤدي طلبات البحث عن العُقد العادية (مثل onNodeWithTag أو fetchSemanticsNode) إلى عمليات مزامنة ضمنية تكون غير ضرورية عند التحكّم في الساعة يدويًا، لذا فإنّ تجاوزها يؤدي إلى تسريع أوقات تشغيل الاختبار بشكل كبير.

إرشادات الاستخدام

  • الإدارة اليدوية للساعة: استخدِم واجهة برمجة التطبيقات هذه عندما يكون mainClock.autoAdvance مضبوطًا على false وتكون واجهة المستخدم في حالة معروفة وثابتة للإطار الحالي.
  • تنفيذ سلسلة واجهة المستخدم: لضمان استقرار شجرة واجهة المستخدم، استدعِ runWithoutImplicitWait في سلسلة واجهة المستخدم، مثلاً باستخدام runOnUiThread. يؤدي تشغيل الاختبار خارج سلسلة التعليمات الخاصة بواجهة المستخدم إلى تعريض الاختبار لحالات التنافس وقراءات الحالة القديمة.
  • تأكيدات للقراءة فقط: يجب أن يحتوي الحظر على تأكيدات للقراءة فقط. يجب تنفيذ أي إجراءات تغيّر الحالة خارج هذا القسم.

مثال

@Test
fun runWithoutImplicitWaitSample() = runComposeUiTest {
    setContent { MainScreen() }
    mainClock.autoAdvance = false

    // Trigger an animation
    onNodeWithText("Start Animation").performClick()

    // Step through the animation frame-by-frame
    while (hasPendingWork()) {
        mainClock.advanceTimeByFrame()
        waitForIdle()
        runOnUiThread {
            // Suppress implicit synchronization inside this block to avoid redundant
            // waits on each node query, making the frame assertions execute much faster.
            runWithoutImplicitWait {
                val box1 = onNodeWithTag("Box1").fetchSemanticsNode()
                val box2 = onNodeWithTag("Box2").fetchSemanticsNode()
                val box3 = onNodeWithTag("Box3").fetchSemanticsNode()

                // Assert the exact intermediate state of all three properties for this frame
                assert(box1.boundsInRoot.right <= box2.boundsInRoot.left)
                assert(box2.boundsInRoot.right <= box3.boundsInRoot.left)
            }
        }
    }
}