O Compose oferece ComposeTestRule, que permite escrever testes de animações
de forma determinística com controle total sobre o relógio de teste. Isso permite
verificar valores de animação intermediários. Além disso, a duração do teste pode ser menor
do que a da animação.
A ComposeTestRule exibe o relógio de teste como mainClock. Você pode definir
a propriedade autoAdvance como "false" no código de teste para controlar o relógio. Depois
de iniciar a animação a ser testada, o relógio pode ser adiantado com
advanceTimeBy.
Observe que o advanceTimeBy não adianta o relógio exatamente de acordo com a
duração especificada. Em vez disso, a duração é arredondada para o valor mais próximo
que corresponda a um multiplicador da duração do 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() }
Otimizar testes de animação
Ao testar animações de alta fidelidade, muitas vezes é necessário desativar o avanço automático e percorrer os frames manualmente para declarar estados intermediários da interface. Para esses
loops específicos frame a frame, use o runWithoutImplicitWait método para
executar suas declarações. Consultas de nós padrão (como onNodeWithTag ou
fetchSemanticsNode) acionam sincronizações implícitas que são redundantes
quando você está controlando o relógio manualmente. Portanto, ignorá-las acelera significativamente
os tempos de execução do teste.
diretrizes de uso
- Gerenciamento manual do relógio: use essa API quando
mainClock.autoAdvanceestiver definido comofalsee a interface estiver em um estado conhecido e estável para o frame atual. - Execução da linha de execução da interface: para garantir a estabilidade da árvore de interface, chame
runWithoutImplicitWaitna linha de execução da interface, como comrunOnUiThread. A execução fora da linha de execução da interface expõe seu teste a condições de corrida e leituras de estado obsoletas. - Declarações somente leitura: o bloco precisa conter declarações somente leitura. Todas as ações que modificam o estado precisam ser realizadas fora desse bloco.
Exemplo
@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) } } } }
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Como testar o layout do Compose
- Outras considerações
- Personalizar animações