Тестирование Android UI
UI-тесты Android проверяют наблюдаемое поведение: пользователь выполняет действие и видит ожидаемое состояние, результат навигации или ошибку. Основную бизнес-логику лучше проверять быстрыми локальными тестами, а тесты на устройстве оставлять для поведения, зависящего от Android UI, жизненного цикла, ресурсов или интеграции экранов.
Объём теста и среда выполнения
JUnit предоставляет жизненный цикл теста, проверки и правила, но сам по себе не управляет Android UI.
- Тесты в
src/testвыполняются локально на JVM. Они подходят для чистого Kotlin-кода и могут использовать Robolectric, когда достаточно имитации Android-среды. - Тесты в
src/androidTestвыполняются на эмуляторе или устройстве через инструментальный раннер. Обычно здесь находятся тесты Espresso и Compose, которым нужно устройство.
Изолированный тест экрана получает контролируемое состояние и поддельные зависимости. Более широкий сценарий запускает приложение и проверяет взаимодействие нескольких компонентов. Таких сценариев должно быть немного: они медленнее и имеют больше возможных причин сбоя.
Espresso для View
Espresso находит объекты View, выполняет действия и проверяет утверждения:
onView(withId(R.id.emailInput))
.perform(typeText("ada@example.com"), closeSoftKeyboard())
onView(withId(R.id.loginButton)).perform(click())
onView(withText("Welcome")).check(matches(isDisplayed()))
Espresso ждёт, пока освободятся главная очередь сообщений, задачи AsyncTask и зарегистрированные экземпляры IdlingResource. Он не знает автоматически о произвольных исполнителях, callback-функциях и внешней асинхронной работе. Предпочитайте контролируемые поддельные зависимости. Если в тесте нужна реальная асинхронная операция, зарегистрируйте idling resource до её запуска и удалите регистрацию после теста. Не используйте Thread.sleep(): он замедляет тест и зависит от времени выполнения.
Используйте устойчивые селекторы: идентификаторы ресурсов, осмысленный текст или описания содержимого. В списках находите элемент по стабильному свойству и применяйте действие RecyclerView, а не полагайтесь на координаты экрана или позицию дочернего элемента.
UI-тесты Compose
Тесты Compose взаимодействуют с деревом семантики, а не с иерархией View:
@get:Rule
val composeRule = createComposeRule()
@Test
fun savingProfileShowsSuccess() {
composeRule.setContent {
ProfileScreen(
state = ProfileUiState(userName = "Ada"),
onSave = { }
)
}
composeRule.onNodeWithText("Save").performClick()
composeRule.onNodeWithText("Saved").assertIsDisplayed()
}
Используйте createComposeRule(), когда тест сам задаёт содержимое. Выбирайте createAndroidComposeRule<Activity>(), когда в сценарии участвуют Activity и интеграция с Android.
Предпочитайте семантику, которая также описывает интерфейс пользователю: текст, роль, описание содержимого, выбранное состояние и описание состояния. Используйте Modifier.testTag(), если устойчивого пользовательского селектора нет, но не считайте тег заменой доступности. Если узел не находится, изучите дерево семантики и проверьте, нужны ли объединённые дочерние элементы с useUnmergedTree = true; запросы к необъединённому дереву не должны быть выбором по умолчанию.
Compose ждёт известную ему работу и учитывает тестовые часы, но не видит каждый внешний асинхронный источник. По возможности внедряйте уже завершённые или управляемые поддельные зависимости. Для наблюдаемого условия, которое действительно завершается вне синхронизации Compose, используйте waitUntil с ограниченным тайм-аутом.
Для детерминированной проверки анимации управляйте виртуальным временем:
Надёжные UI-тесты
- Проверяйте видимый пользователю результат, а не приватные поля, структуру composable-функций или точные координаты пикселей.
- Заменяйте реальные сеть, часы и случайные данные детерминированными зависимостями.
- Запускайте каждый тест из известного состояния приложения; не полагайтесь на порядок тестов или данные, оставленные другим тестом.
- Осознанно проверяйте важные конфигурации, например локаль, тёмную тему, масштаб шрифта и характерные размеры окна, вместо повторения каждого теста на каждом устройстве.
- Используйте снимки экрана для диагностики или отдельного визуального регрессионного тестирования, но не вместо поведенческих проверок.
- Нестабильный тест является дефектом: ищите отсутствующую синхронизацию или общее состояние, а не добавляйте повторы вслепую.
Связанные темы: Стратегия тестирования, Тестирование ViewModel, Тестирование Compose.
Источники: основы тестирования Android, Espresso, idling resources Espresso, тестирование Compose UI, синхронизация тестов Compose.