Разработка 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 для читаемости и повторного использования.







