Compose propose ComposeTestRule, qui vous permet d'écrire des tests pour les animations de manière déterministe en contrôlant totalement l'horloge de test. Cela vous permet de vérifier les valeurs d'animation intermédiaires. En outre, un test peut s'exécuter plus rapidement que la durée réelle de l'animation.
ComposeTestRule expose son horloge de test en tant que mainClock. Vous pouvez définir la propriété autoAdvance sur "false" pour contrôler l'horloge dans votre code de test. Après avoir lancé l'animation à tester, vous pouvez avancer l'horloge avec advanceTimeBy.
Avec advanceTimeBy, vous n'avancez pas exactement l'horloge selon la durée spécifiée. L'heure est arrondie à la durée la plus proche, qui est un multiplicateur de la durée de l'image.
@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() }
Optimiser les tests d'animation
Lorsque vous testez des animations haute fidélité, vous devez souvent désactiver l'avance automatique et parcourir manuellement les frames pour affirmer les états intermédiaires de l'UI. Pour ces boucles spécifiques image par image, utilisez la méthode runWithoutImplicitWait pour exécuter vos assertions. Les requêtes de nœud standards (comme onNodeWithTag ou fetchSemanticsNode) déclenchent des synchronisations implicites qui sont redondantes lorsque vous contrôlez manuellement l'horloge. Les ignorer accélère donc considérablement la durée d'exécution de vos tests.
Consignes d'utilisation
- Gestion manuelle de l'horloge : utilisez cette API lorsque
mainClock.autoAdvanceest défini surfalseet que l'UI est dans un état stable connu pour le frame actuel. - Exécution du thread UI : pour assurer la stabilité de l'arborescence de l'UI, appelez
runWithoutImplicitWaitsur le thread UI, par exemple avecrunOnUiThread. L'exécution en dehors du thread d'UI expose votre test à des conditions de concurrence et à des lectures d'état obsolètes. - Assertions en lecture seule : le bloc doit contenir uniquement des assertions en lecture seule. Toutes les actions qui modifient l'état doivent être effectuées en dehors de ce bloc.
Exemple
@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) } } } }
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Tester votre mise en page Compose
- Autres points à prendre en compte
- Personnaliser les animations