Как настроить тестовую среду с помощью API тестирования версии 2

Теперь доступны версии 2 API тестирования Compose (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest и т. д.), которые позволяют лучше контролировать выполнение сопрограмм. В этом обновлении не дублируется весь API, а только те API, которые создают тестовую среду.

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

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

Как настроить тестовую среду

В API Compose Test версии 2 для настройки среды тестирования используется ComposeUiTestConfig. API, которые создают функции настройки для тестов, например createComposeRule, runComposeUiTest и другие связанные API, принимают ComposeUiTestConfig. Этот объект конфигурации объединяет API, связанные с окружающей средой, например effectContext, runTestContext и testTimeout, в один объект.

Модель конфигурации также управляет inputMode. API Compose test v2 по умолчанию применяют InputMode.Touch в начале каждого теста, чтобы обеспечить детерминизм и предотвратить утечку состояния режима ввода между тестами.

ComposeUiTestConfig входит в состав API Compose testing v2, в которых по умолчанию используется StandardTestDispatcher. Если в ваших тестах используются API версии 1, перед переходом на ComposeUiTestConfig ознакомьтесь с разделом Переход на API тестирования версии 2.

Перенос в ComposeUiTestConfig

В перегрузках для создания функций настройки в тестах несколько перегрузок, принимающих отдельные параметры конфигурации, такие как effectContext, runTestContext или testTimeout, объявлены устаревшими. Замените ее в тестах на таблицу "ComposeUiTestConfig", как показано в следующем примере:

Режим ввода по умолчанию

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

Чтобы устранить эту проблему, укажите нужный режим ввода в ComposeUiTestConfig:

class FocusTest {
    @get:Rule
    val rule = createComposeRule(
        config = ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    )

    @Test
    fun testFocus() {}
}

Чтобы настроить режим ввода для отдельных тестовых случаев, а не для всего тестового класса, передайте ComposeUiTestConfig в runComposeUiTest:

class FocusTest {
    @Test
    fun testTouchMode() = runComposeUiTest {
        // Runs with the default InputMode.Touch
    }

    @Test
    fun testKeyboardMode() = runComposeUiTest(
        ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    ) {
        // Runs with InputMode.Keyboard
    }
}

Другие проблемы с переносом и способы их устранения описаны в разделе Частые проблемы и способы их устранения.

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

При переходе на 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 в Android Studio можно узнать на сайте https://developer.android.com/studio/gemini/overview.

вкладки.

В таблице ниже приведены устаревшие API версии 1 и их аналоги в версии 2.

Устаревшая версия (v1)

Замена (версия 2)

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 и вызываете этот конструктор.

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

Основное различие между версиями 1 и 2 API заключается в том, как отправляются сопрограммы:

  • 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 блок кода выполняется в потоке UI после того, как интерфейс перейдет в режим ожидания.

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

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

Перенос в runComposeUiTest

Если вы используете тестовые API Compose вместе с API Kotlin runTest, настоятельно рекомендуем перейти на 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()
    }
}