Тестовые анимации

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 для выполнения проверок. Стандартные запросы к узлам (например onNodeWithTag или fetchSemanticsNode ) запускают неявную синхронизацию, которая избыточна при ручном управлении временем, поэтому их обход значительно ускоряет выполнение тестов.

Правила использования

  • Ручное управление временем : используйте этот API, когда mainClock.autoAdvance установлено в false , и пользовательский интерфейс находится в известном, стабильном состоянии для текущего кадра.
  • Выполнение в потоке пользовательского интерфейса : Для обеспечения стабильности дерева пользовательского интерфейса вызовите 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)
            }
        }
    }
}

{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}