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







