ทดสอบภาพเคลื่อนไหว

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()
}

เพิ่มประสิทธิภาพการทดสอบภาพเคลื่อนไหว

เมื่อทดสอบแอนิเมชันที่มีความสมจริงสูง คุณมักจะต้องปิดใช้การเลื่อนไปข้างหน้าอัตโนมัติและเลื่อนดูเฟรมทีละเฟรมด้วยตนเองเพื่อยืนยันสถานะ UI ระหว่างกลาง สำหรับลูปทีละเฟรมที่เฉพาะเจาะจงเหล่านี้ ให้ใช้เมธอด runWithoutImplicitWait เพื่อ ดำเนินการยืนยัน การค้นหาโหนดมาตรฐาน (เช่น onNodeWithTag หรือ fetchSemanticsNode) จะทริกเกอร์การซิงโครไนซ์โดยนัยซึ่งซ้ำซ้อน เมื่อคุณควบคุมนาฬิกาด้วยตนเอง ดังนั้นการข้ามการค้นหาเหล่านี้จะช่วยเพิ่มความเร็วในการรันการทดสอบได้อย่างมาก

หลักเกณฑ์การใช้งาน

  • การจัดการนาฬิกาด้วยตนเอง: ใช้ API นี้เมื่อตั้งค่า mainClock.autoAdvance เป็น false และ UI อยู่ในสถานะที่ทราบและเสถียรสำหรับ เฟรมปัจจุบัน
  • การดำเนินการในเธรด UI: เรียก runWithoutImplicitWait ในเธรด UI เช่น ด้วย runOnUiThread เพื่อให้มั่นใจว่าต้นไม้ UI มีความเสถียร การเรียกใช้ใน Thread อื่นที่ไม่ใช่ UI Thread จะทำให้การทดสอบของคุณเสี่ยงต่อสภาวะการแข่งขันและการอ่านสถานะที่ล้าสมัย
  • การยืนยันแบบอ่านอย่างเดียว: บล็อกควรมีการยืนยันแบบอ่านอย่างเดียว เท่านั้น การดำเนินการใดๆ ที่เปลี่ยนแปลงสถานะควรทำนอกบล็อกนี้

ตัวอย่าง

@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)
            }
        }
    }
}