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 maju otomatis dan menelusuri frame secara manual 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 clock secara manual, sehingga melewati sinkronisasi tersebut akan mempercepat waktu proses pengujian secara signifikan.

Panduan penggunaan

  • Pengelolaan clock manual: Gunakan API ini saat mainClock.autoAdvance disetel ke false dan UI berada dalam status yang diketahui dan stabil untuk frame saat ini.
  • Eksekusi UI thread: Untuk memastikan stabilitas hierarki UI, panggil runWithoutImplicitWait di UI thread, seperti dengan runOnUiThread. Menjalankannya di luar thread UI akan membuat pengujian Anda rentan terhadap kondisi race dan pembacaan status yang tidak valid.
  • Pernyataan hanya baca: Blok harus berisi pernyataan hanya baca. Setiap tindakan 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)
            }
        }
    }
}