Переход на API тестирования версии 2.

Теперь доступны версии v2 API для тестирования Compose ( createComposeRule , createAndroidComposeRule , runComposeUiTest , runAndroidComposeUiTest и т. д.), что улучшает контроль над выполнением сопрограмм. Это обновление не дублирует весь интерфейс API; обновлены только те API, которые определяют среду тестирования.

API версии 1 устарели, и настоятельно рекомендуется перейти на новые API. Переход гарантирует соответствие ваших тестов стандартному поведению сопрограмм и позволяет избежать проблем с совместимостью в будущем. Список устаревших API версии 1 см. в разделе «Сопоставление API» .

Эти изменения включены в androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ и androidx.compose.ui:ui-test:1.11.0-alpha03+ .

В то время как API версии 1 использовали UnconfinedTestDispatcher , API версии 2 по умолчанию используют StandardTestDispatcher для запуска композиции тестов. Это изменение приводит поведение Compose в соответствие со стандартными API runTest и обеспечивает явный контроль над порядком выполнения сопрограмм.

сопоставления API

При обновлении до API версии 2 обычно можно использовать функцию «Найти + Заменить» , чтобы обновить импорт пакетов и применить новые изменения в диспетчере.

В качестве альтернативы, попросите Gemini выполнить миграцию на версию 2 API тестирования Compose, используя следующую команду:

Переход с API тестирования версии 1 на API тестирования версии 2.

В этом задании мы будем использовать данное руководство для перехода на API тестирования версии 2.

Migrate to Compose testing v2 APIs using the official
migration guide.

Использование подсказок ИИ

Подсказки ИИ предназначены для использования в Gemini в Android Studio.

Подробнее о Gemini в Studio можно узнать здесь: https://developer.android.com/studio/gemini/overview

Поделитесь своим мнением

Используйте следующую таблицу для сопоставления устаревших API версии 1 с их заменами в версии 2:

Устарело (v1)

Замена (v2)

androidx.compose.ui.test.junit4.createComposeRule

androidx.compose.ui.test.junit4.v2.createComposeRule

androidx.compose.ui.test.junit4.createAndroidComposeRule

androidx.compose.ui.test.junit4.v2.createAndroidComposeRule

androidx.compose.ui.test.junit4.createEmptyComposeRule

androidx.compose.ui.test.junit4.v2.createEmptyComposeRule

androidx.compose.ui.test.junit4.AndroidComposeTestRule

androidx.compose.ui.test.junit4.v2.AndroidComposeTestRule

androidx.compose.ui.test.runComposeUiTest

androidx.compose.ui.test.v2.runComposeUiTest

androidx.compose.ui.test.runAndroidComposeUiTest

androidx.compose.ui.test.v2.runAndroidComposeUiTest

androidx.compose.ui.test.runEmptyComposeUiTest

androidx.compose.ui.test.v2.runEmptyComposeUiTest

androidx.compose.ui.test.AndroidComposeUiTestEnvironment

androidx.compose.ui.test.v2.AndroidComposeUiTestEnvironment

Обратная совместимость и исключения

Существующие API версии 1 устарели, но продолжают использовать UnconfinedTestDispatcher для сохранения существующего поведения и предотвращения критических изменений.

Следующее является единственным исключением, где поведение по умолчанию изменилось:

В классе AndroidComposeUiTestEnvironment диспетчер тестов по умолчанию, используемый для запуска композиции, изменился с UnconfinedTestDispatcher на StandardTestDispatcher . Это влияет на случаи, когда вы создаете экземпляр с помощью конструктора или создаете подкласс AndroidComposeUiTestEnvironment и вызываете этот конструктор.

Ключевое изменение: Влияние на выполнение сопрограмм.

Основное различие между версиями API v1 и v2 заключается в способе отправки сопрограмм:

  • API версии 1 ( UnconfinedTestDispatcher ): При запуске сопрограммы она выполнялась немедленно в текущем потоке, часто завершаясь до того, как выполнялась следующая строка тестового кода. В отличие от поведения в производственной среде, такое немедленное выполнение может непреднамеренно маскировать реальные проблемы со временем выполнения или состояния гонки , которые возникли бы в работающем приложении.
  • API версии 2 ( StandardTestDispatcher ): При запуске сопрограммы она ставится в очередь и не выполняется до тех пор, пока тест явно не переведет виртуальные часы вперед. Стандартные API Compose для тестов (например, waitForIdle() ) уже обрабатывают эту синхронизацию, поэтому большинство тестов, использующих эти стандартные API, должны продолжать работать без изменений.

Распространенные неисправности и способы их устранения

Если после обновления до версии 2 ваши тесты перестают работать, скорее всего, они будут демонстрировать следующую закономерность:

  • Ошибка : Вы запускаете задачу (например, ViewModel загружает данные), но ваша проверка немедленно завершается с ошибкой, поскольку данные все еще находятся в состоянии "Загрузка".
  • Причина : В API версии 2 сопрограммы ставятся в очередь, а не выполняются немедленно. Задача была поставлена ​​в очередь, но фактически так и не была выполнена до проверки результата.
  • Исправление : Явно указывайте время вперед. Необходимо явно указать диспетчеру версии 2, когда следует выполнять работу.

Предыдущий подход

В версии 1 задача запускалась и завершалась немедленно. В версии 2 следующий код завершается с ошибкой, поскольку loadData() еще фактически не была выполнена.

// In v1, this launched and finished immediately.
viewModel.loadData()

// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)

Используйте waitForIdle или runOnIdle для выполнения задач из очереди перед подтверждением.

Вариант 1 : Использование waitForIdle переводит таймер вперед до тех пор, пока пользовательский интерфейс не перейдет в режим ожидания, проверяя, что сопрограмма выполнилась.

viewModel.loadData()

// Explicitly run all queued tasks
composeTestRule.waitForIdle()

assertEquals(Success, viewModel.state.value)

Вариант 2 : Использование runOnIdle выполняет блок кода в потоке пользовательского интерфейса после того, как пользовательский интерфейс перейдет в режим ожидания.

viewModel.loadData()

// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
    assertEquals(Success, viewModel.state.value)
}

Ручная синхронизация

В сценариях, требующих ручной синхронизации, например, когда автоматическое переключение между временными интервалами отключено, запуск сопрограммы не приводит к немедленному выполнению, поскольку тестовые часы приостановлены. Для выполнения сопрограмм в очереди без переключения виртуальных часов используйте API runCurrent() . Эта функция запускает задачи, запланированные на текущее виртуальное время.

composeTestRule.mainClock.scheduler.runCurrent()

В отличие от waitForIdle() , который переводит тестовые часы вперед до тех пор, пока пользовательский интерфейс не стабилизируется, runCurrent() выполняет ожидающие задачи, сохраняя при этом текущее виртуальное время. Такое поведение позволяет проверять промежуточные состояния, которые в противном случае были бы пропущены, если бы часы были переведены в состояние ожидания.

В тестовой среде доступен базовый планировщик тестов. Этот планировщик можно использовать совместно с API Kotlin runTest для синхронизации тестовых часов.

Перейдите на использование runComposeUiTest

Если вы используете API для тестирования Compose вместе с API runTest Kotlin, настоятельно рекомендуется перейти на runComposeUiTest .

Предыдущий подход

Использование createComposeRule совместно с runTest создает два отдельных таймера: один для Compose, а другой для области действия сопрограммы теста. Такая конфигурация может вынудить вас вручную синхронизировать планировщик тестов.

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testWithCoroutines() {
    composeTestRule.setContent {
        var status by remember { mutableStateOf("Loading...") }
        LaunchedEffect(Unit) {
            delay(1000)
            status = "Done!"
        }
        Text(text = status)
    }

    // NOT RECOMMENDED
    // Fails: runTest creates a new, separate scheduler.
    // Advancing time here does NOT advance the compose clock.
    // To fix this without migrating, you would need to share the scheduler
    // by passing 'composeTestRule.mainClock.scheduler' to runTest.
    runTest {
        composeTestRule.onNodeWithText("Loading...").assertIsDisplayed()
        advanceTimeBy(1000)
        composeTestRule.onNodeWithText("Done!").assertIsDisplayed()
    }
}

API runComposeUiTest автоматически выполняет ваш тестовый блок в рамках собственной области runTest . Время выполнения теста синхронизируется с окружением Compose, поэтому вам больше не нужно управлять планировщиком вручную.

    @Test
    fun testWithCoroutines() = runComposeUiTest {
        setContent {
            var status by remember { mutableStateOf("Loading...") }
            LaunchedEffect(Unit) {
                delay(1000)
                status = "Done!"
            }
            Text(text = status)
        }

        onNodeWithText("Loading...").assertIsDisplayed()
        mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */)
        onNodeWithText("Done!").assertIsDisplayed()
    }
}