Разработка UI-тестов для Android-приложения (Espresso)
Вы написали экран логина на Compose, настроили Hilt, добавили Retrofit. Запускаете тест — он падает. Почему? UI-тесты на Android часто flaky — корень почти всегда в игнорировании IdlingResource. Разработчики вставляют Thread.sleep(2000) и надеются, что пройдёт — это путь к хаосу. Без правильно настроенных IdlingResource стабильность редко превышает 60%. Тест-сьют на 100+ тестов требует 2–3 часа на прогон, половина падает из-за тайминга.
Мы предлагаем разработку UI-тестов на Espresso под ключ. Спроектируем архитектуру с паттерном Robot, настроим IdlingResource для всех асинхронных операций, интегрируем с Firebase Test Lab. За 3–5 дней получаете стабильный набор critical user flows (логин, оплата, основной UX). Один из наших проектов — приложение интернет-банка с 1200 тестами — достиг стабильности 98.5% после настройки IdlingResource.
Google официально рекомендует использовать IdlingResource для синхронизации тестов с фоновыми задачами — это основа стабильного автотестирования.
Основы и критичные нюансы
Базовая структура теста Espresso: onView(matcher).perform(action).check(assertion).
@Test
fun loginWithValidCredentials_navigatesToHome() {
onView(withId(R.id.emailInput))
.perform(typeText("[email protected]"), closeSoftKeyboard())
onView(withId(R.id.passwordInput))
.perform(typeText("password123"), closeSoftKeyboard())
onView(withId(R.id.loginButton))
.perform(click())
onView(withId(R.id.homeTitle))
.check(matches(isDisplayed()))
}
Самая частая причина нестабильных тестов — IdlingResource. Если приложение делает асинхронный запрос (Retrofit, Coroutine), Espresso не знает об этом и пытается найти следующий элемент до завершения операции. Решение — зарегистрировать IdlingResource.
// Для OkHttp/Retrofit используйте OkHttpIdlingResource
val idlingResource = OkHttpIdlingResource.create("okhttp", okHttpClient)
IdlingRegistry.getInstance().register(idlingResource)
// Для корутин используйте EspressoIdlingResource
IdlingRegistry.getInstance().register(EspressoIdlingResource.getIdlingResource())
Espresso работает в 2–3 раза быстрее UIAutomator за счёт отсутствия sleep.
Почему тесты на Espresso падают?
Помимо IdlingResource, частая проблема — race conditions при работе с RecyclerView или многопоточностью. Используйте IdlingResource для асинхронных операций и CountingIdlingResource для фоновых задач. Наш опыт: после внедрения IdlingResource стабильность тестов вырастает с 60% до 95%.
| Проблема | Решение |
|---|---|
| Асинхронные запросы (Retrofit, Coroutine) | OkHttpIdlingResource / EspressoIdlingResource |
| Загрузка изображений (Glide, Coil) | GlideIdlingResource |
| Анимации (Transition, Lottie) | Отключить анимации в тестах |
| RecyclerView с подгрузкой | CountingIdlingResource |
Jetpack Compose UI testing
Если проект использует Compose, Espresso-подход заменяется на ComposeTestRule:
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun loginScreen_showsErrorOnInvalidEmail() {
composeTestRule.onNodeWithTag("emailInput")
.performTextInput("invalid-email")
composeTestRule.onNodeWithTag("loginButton")
.performClick()
composeTestRule.onNodeWithText("Неверный формат email")
.assertIsDisplayed()
}
testTag в Compose — аналог accessibilityIdentifier в iOS. Обязательно проставляем на все тестируемые элементы через Modifier.testTag("loginButton").
Как ускорить написание тестов?
Используем Robot pattern — аналог Page Object для Compose. Он делает тесты читаемыми как сценарии и упрощает рефакторинг. Сравнение:
| Критерий | Robot pattern | Page Object |
|---|---|---|
| Синтаксис | Цепочки вызовов (Builder) | Методы без возврата объекта |
| Читаемость | Высокая, похожа на шаги тест-кейса | Средняя, требуется дополнительная структура |
| Переиспользование | Через композицию роботов | Через наследование page-классов |
| Поддержка Compose | Нативная, без лишних обёрток | Требуется адаптация |
Robot pattern подходит для Compose-проектов лучше. Пример:
class LoginRobot(private val composeTestRule: AndroidComposeTestRule<*, *>) {
fun enterEmail(email: String) = apply {
composeTestRule.onNodeWithTag("emailInput").performTextInput(email)
}
fun enterPassword(password: String) = apply {
composeTestRule.onNodeWithTag("passwordInput").performTextInput(password)
}
fun clickLogin() = apply {
composeTestRule.onNodeWithTag("loginButton").performClick()
}
fun assertHomeVisible() {
composeTestRule.onNodeWithTag("homeScreen").assertIsDisplayed()
}
}
// Тест читается как сценарий
@Test
fun validLogin_showsHome() {
LoginRobot(composeTestRule)
.enterEmail("[email protected]")
.enterPassword("pass123")
.clickLogin()
.assertHomeVisible()
}
Hilt и DI в инструментальных тестах
Если проект использует Hilt, тесты требуют HiltAndroidRule. Аннотация @BindValue позволяет подменить реальный репозиторий на фейковый без изменения основного кода — тест не зависит от сети или базы данных.
Как написать стабильный тест на Espresso за 5 шагов
- Определите сценарий и выделите асинхронные операции.
- Зарегистрируйте IdlingResource для каждой операции.
- Используйте Robot pattern для структурирования.
- Запустите тест локально, проверьте стабильность.
- Интегрируйте с CI и Firebase Test Lab.
CI: Firebase Test Lab
Локальный эмулятор достаточен для разработки, но перед релизом — Firebase Test Lab с реальными устройствами. Пример workflow (GitHub Actions):
- name: Run Espresso tests on Firebase Test Lab
run: |
gcloud firebase test android run \
--type instrumentation \
--app app/build/outputs/apk/debug/app-debug.apk \
--test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk \
--device model=Pixel8,version=34 \
--device model=GalaxyS23,version=33
Результаты — Logcat, скриншоты при падении, видео прогона — сохраняются в Google Cloud Storage. Настройка CI входит в объём работ.
Что входит в разработку UI-тестов на Espresso
- Проектирование архитектуры тестов (выбор паттерна, настройка IdlingResource)
- Написание critical user flows (логин, оплата, основной UX) с Robot pattern
- Интеграция с CI/CD (GitHub Actions, Firebase Test Lab)
- Документация по запуску и поддержке тестов
- Обучение команды (1–2 часа воркшопа)
Мы разработали UI-тесты для 15+ Android-приложений, средняя стабильность — 97%, максимальный проект — 1200 тестов. Среднее время прогона 100 тестов — 15 минут вместо 2 часов без оптимизации.
Срок: от 3 до 10 дней в зависимости от сложности приложения. Стоимость рассчитывается индивидуально. Если вам нужна помощь с внедрением — свяжитесь с нами, мы проведём аудит и предложим решение. Закажите разработку тестового suite — гарантируем стабильность >95% и месяц сопровождения.
Чек-лист для стабильных UI-тестов
- Всегда регистрируйте IdlingResource для всех фоновых потоков.
- Не используйте Thread.sleep() — используйте явное ожидание через IdlingResource.
- Убедитесь, что анимации отключены в тестовом профиле устройства.
- Запускайте тесты на реальных устройствах через Firebase Test Lab.
- Очищайте состояние между тестами (clearState в @Before).
- Используйте Robot pattern для читаемости и повторного использования.







