Розробка UI-тестів для Android-додатку (Espresso)
Espresso — інструментальний UI-тест-фреймворк від Google, вбудований в Android SDK. Працює в тому ж процесі, що й додаток, що дає йому перевагу: синхронізація з UI-потоком автоматична. Espresso знає, коли додаток «занятий» — на відміну від UIAutomator, він не потребує sleep() між діями.
Основи та критичні нюанси
Базова структура тесту 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
val idlingResource = OkHttpIdlingResource.create("okhttp", okHttpClient)
IdlingRegistry.getInstance().register(idlingResource)
Для корутин — IdlingCoroutineDispatcher або EspressoIdlingResource від Google.
Compose UI Testing
Якщо проект використовує Jetpack 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").
Hilt та DI у інструментальних тестах
Якщо проект використовує Hilt, тесты вимагають HiltAndroidRule:
@HiltAndroidTest
class LoginScreenTest {
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<MainActivity>()
@BindValue
val authRepository: AuthRepository = FakeAuthRepository()
@Before
fun init() { hiltRule.inject() }
}
@BindValue дозволяє підменити реальний репозиторій на фейковий без зміни основного коду — тест не залежить від мережі або бази даних.
Robot Pattern (аналог Page Object)
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()
}
CI: Firebase Test Lab
Локальний емулятор достатній для розробки, але перед релізом — Firebase Test Lab з реальними пристроями:
- 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.
Scope тестування
Critical flows: логін/реєстрація, оплата, основний користувальницький шлях. Edge cases: deep link при незалогіненому користувачі, натиск на push notification, повернення з background. Доступність: TalkBack через UIAutomator + AccessibilityChecks від Google (AccessibilityChecks.enable() у setUp()).
Срок: 3–5 днів для базового suite critical user flows з Robot pattern, Hilt-інтеграцією та CI на Firebase Test Lab.







