پویانمایی‌های آزمایشی

‫Compose 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 تنظیم شده است و واسط کاربر در وضعیت شناخته‌شده و پایداری برای قاب کنونی قرار دارد، از این API استفاده کنید.
  • اجرای رشته میانای کاربر: برای اطمینان از پایداری درخت میانای کاربر، 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)
            }
        }
    }
}