Compose는 테스트 클록을 완전히 제어할 수 있는 확정적인 방식으로 애니메이션 테스트를 작성할 수 있는 ComposeTestRule을 제공합니다. 이를 통해 중간 애니메이션 값을 확인할 수 있습니다. 또한 애니메이션의 실제 기간보다 시간보다 더 빠르게 테스트를 실행할 수 있습니다.
ComposeTestRule은 테스트 클록을 mainClock으로 노출합니다. 테스트 코드에서 autoAdvance 속성을 false로 설정하여 클록을 제어할 수 있습니다. 테스트할 애니메이션을 시작한 후 advanceTimeBy를 사용하여 클록을 앞으로 이동할 수 있습니다.
참고: advanceTimeBy는 클록을 이동할 때 지정된 기간을 정확히 따르지 않습니다. 대신 프레임 시간의 배수인 가장 가까운 기간으로 반올림합니다.
@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() }
애니메이션 테스트 최적화
고화질 애니메이션을 테스트할 때는 자동 진행을 사용 중지하고 프레임을 수동으로 단계별로 실행하여 중간 UI 상태를 어설션해야 하는 경우가 많습니다. 이러한
특정 프레임별 루프의 경우 runWithoutImplicitWait 메서드를 사용하여
어설션을 실행합니다. 표준 노드 쿼리 (예: onNodeWithTag 또는
fetchSemanticsNode)는 시계를 수동으로 제어할 때 중복되는 암시적 동기화를 트리거하므로 이를 우회하면 테스트 런타임이 크게 빨라집니다.
사용 가이드라인
- 수동 시계 관리:
mainClock.autoAdvance가false로 설정되어 있고 UI가 현재 프레임에 대해 알려진 안정적인 상태에 있을 때 이 API를 사용합니다. - UI 스레드 실행: UI 트리의 안정성을 보장하려면
runWithoutImplicitWait와 같이 UI 스레드에서runOnUiThread를 호출합니다. UI 스레드 외부에서 실행하면 테스트가 경합 상태 및 오래된 상태 읽기에 노출됩니다. - 읽기 전용 어설션: 블록에는 읽기 전용 어설션만 포함되어야 합니다. 상태를 변경하는 작업은 이 블록 외부에서 실행해야 합니다.
예
@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) } } } }
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- Compose 레이아웃 테스트
- 기타 고려사항
- 애니메이션 맞춤설정하기