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







