Animationen testen

Compose bietet ComposeTestRule, mit dem Sie Tests für Animationen deterministisch und mit vollständiger Kontrolle über die Testuhr schreiben können. So können Sie Zwischenwerte der Animation überprüfen. Außerdem kann ein Test schneller als die tatsächliche Dauer der Animation ausgeführt werden.

ComposeTestRule stellt seine Testuhr als mainClock zur Verfügung. Sie können das Attribut autoAdvance auf „false“ setzen, um die Zeit in Ihrem Testcode zu steuern. Nachdem Sie die Animation gestartet haben, die Sie testen möchten, kann die Uhr mit advanceTimeBy vorgespult werden.

advanceTimeBy verschiebt die Uhr nicht genau um die angegebene Dauer. Stattdessen wird die Dauer auf die nächste Dauer aufgerundet, die ein Vielfaches der Frame-Dauer ist.

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

Animationstests optimieren

Beim Testen von Animationen mit hoher Wiedergabetreue musst du häufig die automatische Wiedergabe deaktivieren und manuell durch die Frames gehen, um Zwischenzustände der Benutzeroberfläche zu bestätigen. Verwende für diese spezifischen Frame-by-Frame-Schleifen die runWithoutImplicitWait Methode, um deine Zusicherungen auszuführen. Standardmäßige Knotenabfragen wie onNodeWithTag oder fetchSemanticsNode lösen implizite Synchronisierungen aus, die redundant sind, wenn du die Uhr manuell steuerst. Wenn du sie umgehst, werden deine Tests deutlich schneller ausgeführt.

Nutzungsrichtlinien

  • Manuelle Uhrverwaltung: Verwende diese API, wenn mainClock.autoAdvance auf false gesetzt ist und sich die Benutzeroberfläche für den aktuellen Frame in einem bekannten, stabilen Zustand befindet.
  • Ausführung im UI-Thread: Um die Stabilität der UI-Struktur zu gewährleisten, rufe runWithoutImplicitWait im UI-Thread auf, z. B. mit runOnUiThread. Wenn du die Funktion außerhalb des UI-Threads ausführst, ist dein Test von Race-Bedingungen und veralteten Zustandslesevorgängen betroffen.
  • Schreibgeschützte Zusicherungen: Der Block sollte ausschließlich schreibgeschützte Zusicherungen enthalten. Alle Aktionen, die den Zustand ändern, sollten außerhalb dieses Blocks ausgeführt werden.

Beispiel

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