Menguji animasi

Compose menawarkan ComposeTestRule yang memungkinkan Anda menulis pengujian untuk animasi secara deterministik dengan kontrol penuh selama jam pengujian. Hal ini memungkinkan Anda memverifikasi nilai animasi menengah. Selain itu, pengujian dapat berjalan lebih cepat dibandingkan durasi animasi yang sebenarnya.

ComposeTestRule menampilkan jam pengujian sebagai mainClock. Anda dapat menyetel properti autoAdvance ke false untuk mengontrol jam di kode pengujian. Setelah memulai animasi yang ingin Anda uji, jam dapat digerakkan dengan advanceTimeBy.

Satu hal yang perlu diperhatikan di sini adalah advanceTimeBy tidak menggerakkan jam persis dengan durasi yang ditentukan. Sebaliknya, ini membulatkannya ke durasi terdekat yang merupakan pengali dari durasi frame.

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

Mengoptimalkan pengujian animasi

Saat menguji animasi fidelitas tinggi, Anda sering kali perlu menonaktifkan auto-advance dan melangkah secara manual melalui frame untuk menegaskan status UI perantara. Untuk loop frame demi frame tertentu ini, gunakan metode runWithoutImplicitWait untuk menjalankan pernyataan Anda. Kueri node standar (seperti onNodeWithTag atau fetchSemanticsNode) memicu sinkronisasi implisit yang berlebihan saat Anda mengontrol jam secara manual, sehingga melewati sinkronisasi tersebut akan mempercepat runtime pengujian secara signifikan.

Panduan penggunaan

  • Pengelolaan jam manual: Gunakan API ini saat mainClock.autoAdvance ditetapkan ke false dan UI berada dalam status yang diketahui dan stabil untuk frame saat ini.
  • Eksekusi UI thread: Untuk memastikan stabilitas pohon UI, panggil runWithoutImplicitWait di UI thread, seperti dengan runOnUiThread. Menjalankannya di luar UI thread akan mengekspos pengujian Anda ke kondisi race dan pembacaan status yang tidak valid.
  • Pernyataan hanya baca: Blok harus berisi pernyataan hanya baca saja. Tindakan apa pun yang mengubah status harus dilakukan di luar blok ini.

Contoh

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