পরীক্ষা অ্যানিমেশন

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

অ্যানিমেশন পরীক্ষা অপ্টিমাইজ করুন

测试高保真动画时,您通常需要停用自动前进功能,并手动逐帧浏览,以断言中间界面状态。对于这些特定的逐帧循环,请使用 runWithoutImplicitWait 方法来执行断言。当您手动控制时钟时,标准节点查询(例如 onNodeWithTagfetchSemanticsNode)会触发冗余的隐式同步,因此绕过这些查询可以显著缩短测试运行时长。

使用指南

  • 手动时钟管理:当 mainClock.autoAdvance 设置为 false 且界面处于当前帧的已知稳定状态时,请使用此 API。
  • 界面线程执行:为确保界面树的稳定性,请在界面线程上调用 runWithoutImplicitWait,例如使用 runOnUiThread。在界面线程之外运行它会使您的测试面临竞态条件和过时的状态读取。
  • 只读断言:相应代码块应严格包含只读断言。任何会改变状态的操作都应在此代码块之外执行。

示例

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

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}