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 樹狀結構的穩定性,請在 UI 執行緒上呼叫
runWithoutImplicitWait,例如使用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 版面配置
- 其他考量
- 自訂動畫