Espresso је оквир за аутоматизовано UI тестирање Android апликација, развијен од стране Google тима и део AndroidX Test-а. За разлику од инструменталних тестова који проверавају изоловане компоненте, Espresso интерагује са стварним UI-јем: притиска дугмад, уноси текст, проверава приказ елемената. Према Google Android Developers, Espresso обезбеђује аутоматску синхронизацију са UI током, што елиминише потребу за ручним Thread.sleep().
Главно
Espresso је библиотека за писање аутоматизованих UI тестова за Android, која је део Google AndroidX Test-а. Она пружа API за проналажење View елемената на екрану, извршавање акција над њима (клик, унос, свајп) и проверу њиховог стања (приказује се, садржи текст, активан је).
Кључна карактеристика Espresso-а је аутоматска синхронизација са главним током апликације. Оквир чека завршетак свих асинхроних задатака (корутине, AsyncTask, Handler) пре него што изврши следећу проверу. Ово елиминише flaky тестове повезане са race condition-ом и чини UI тестове стабилним и поузданим — ниједан тест не садржи Thread.sleep() или петље чекања.
Espresso следи принцип Three-Legged Dog — тест се састоји од три корака: пронаћи елемент (ViewMatcher), извршити акцију (ViewAction), проверити резултат (ViewAssertion). Сва три корака се записују у ланац позива onView().perform().check(). Овај концепт чини тестове предвидљивим и лако читљивим — сваки тест јасно описује шта тражи, шта ради и шта проверава.
Архитектура Espresso-а се заснива на три компоненте: Espresso (улозна тачка — статички методи onView и onData), ViewMatchers (претрага елемената), ViewActions (акције) и ViewAssertions (провере). Унутра, оквир користи Idling Resource за синхронизацију са UI током.
Најједноставнији тест проналази дугме по ID-у, извршава клик и проверава да се појавио текст „Готово“. Све операције се извршавају синхроно са становишта теста — Espresso гарантује да је UI ток завршио обраду догађаја пре него што тест настави извршење. Ово се постиже уграђеним механизмом чекања: onView блокира извршење теста док UI не постане стабилан.
@Test
fun buttonClick_showsSuccessText() {
// Пронаћи дугме по ID-у и кликнути
onView(withId(R.id.button_submit))
.perform(click())
// Проверити да се текст „Готово“ приказује
onView(withText("Готово"))
.check(matches(isDisplayed()))
}
За покретање Espresso теста користи се ActivityScenario (AndroidX Test) који креира Activity у жељеном стању — покренута, суспендована, уништена. ActivityScenario омогућава тестирање животног циклуса Activity-ја поред чистог UI-ја. На пример, може се проверити да се подаци чувају при ротацији екрана (рекреација Activity-ја) и враћају након уништења.
ViewMatchers су скуп метода из класе Espresso.onView који омогућавају проналажење View-а на екрану по различитим критеријумима: идентификатору ресурса (R.id), тексту, hint-упутству, родитељском елементу и хијерархији. Ако један matcher не даје јединствен резултат, matcher-и се комбинују кроз allOf().
| Matcher | Намена |
|---|---|
| withId(R.id.name) | Претрага по ID-у ресурса |
| withText(„текст“) | Претрага по приказаном тексту |
| withHint(„упутство“) | Претрага по hint атрибуту EditText-а |
| isDisplayed() | Провера да ли је елемент видљив на екрану |
| hasSibling(matcher) | Претрага по суседном елементу |
| allOf(m1, m2) | Комбинација више matcher-а |
Ако на екрану постоји више истих елемената (на пример, два TextView-а са различитим текстом), згодно је комбиновати matcher-е кроз allOf: onView(allOf(withId(R.id.title), withText(„Здраво“))). Ово гарантује избор јединственог елемента. Обрнути оператор — not() — искључује елементе из претраге, а hasSibling() тражи елемент поред познатог.
ViewActions су акције које Espresso извршава над пронађеним View-ом: click(), typeText(), clearText(), scrollTo(), swipeLeft() и друге. Акције се прослеђују методи perform() која може прихватити више акција у низу.
Метод perform() прихвата vararg ViewAction, што омогућава извршавање низа акција над једним елементом: очистити поље, унети нови текст, затворити тастатуру и кликнути дугме. Све акције се извршавају редом којим су наведене, а Espresso гарантује да је претходна акција завршена пре почетка следеће.
// Унос текста у EditText и клик на дугме
onView(withId(R.id.edit_email))
.perform(
clearText(),
typeText("user@example.com"),
closeSoftKeyboard()
)
onView(withId(R.id.button_login))
.perform(click())
За елементе унутар AdapterView-а (ListView, RecyclerView) користи се метод onData() уместо onView. Он ради са подацима адаптера, а не са View-ом — проналази елемент по садржају модела и враћа одговарајући View за даље акције. onData користи hamcrest matcher-е за проналажење елемента по пољима модела података.
ViewAssertions проверавају да ли се View налази у одређеном стању. Основни метод — matches(matcher) — проверава да ли елемент одговара задатом matcher-у. Додатно, Espresso нуди doesNotExist() (елемент не постоји) и selectedDescendantsMatch() (провера угњежђених елемената).
Најчешће провере у UI тестовима: елемент се приказује (isDisplayed), елемент садржи одређени текст (withText), елемент је активан (isEnabled), елемент није изабран (isNotChecked). Свака провера баца детаљан изузетак у случају неуспеха — са навођењем хијерархије View-а на екрану. Ово поједностављује отклањање грешака: у поруци о грешци се види који су елементи стварно били на екрану у тренутку провере.
Ако стандардне провере нису довољне, може се креирати сопствена преко интерфејса ViewAssertion. Прилагођени assertion прима View и може проверити његово стање програмски — на пример, боју текста, маргине или стање прилагођене компоненте која није изложена кроз стандардне matcher-е.
// Провера: TextView се приказује и садржи текст
onView(withId(R.id.text_welcome))
.check(matches(isDisplayed()))
.check(matches(withText("Добродошли")))
// Провера: елемент се НЕ приказује
onView(withId(R.id.progress_bar))
.check(doesNotExist())
Idling Resource је механизам Espresso-а за синхронизацију теста са асинхроним операцијама. Подразумевано, Espresso чека завршетак Handler-а, AsyncTask-а и корутина (кроз coroutinesIdlingResource). Ако апликација обавља позадински рад кроз сопствене токове или Callback сервисе, потребно је регистровати прилагођени Idling Resource.
Од AndroidX Test 1.4.0, Espresso подржава корутине кроз CoroutinesIdlingResource. Тест аутоматски чека завршетак свих покренутих корутина пре него што изврши провере UI-ја. За сложеније сценарије користе се CountingIdlingResource — бројач који се увећава при покретању задатка и смањује при завршетку.
// Регистрација IdlingResource за OkHttp
class OkHttpIdlingResource(
private val client: OkHttpClient
) : IdlingResource {
private var isIdle = true
private var watcher: IdlingResource.ResourceCallback? = null
override fun getName() = "OkHttp"
override fun isIdleNow() = isIdle
override fun registerIdleTransitionCallback(
callback: IdlingResource.ResourceCallback
) {
watcher = callback
}
}
Повезивање Espresso-а у Android пројекту се врши додавањем зависности у build.gradle нивоа модула. Espresso је део AndroidX Test-а, па је довољно навести зависности за језгро Espresso-а, проширења и JUnit интеграцију. Тестови се смештају у директоријум src/androidTest и покрећу на физичком уређају или емулатору преко AndroidJUnitRunner-а.
Минимални скуп зависности укључује espresso-core (језгро), espresso-contrib (додатни matcher-и за RecyclerView, Drawer, Picker) и runner (тестни раннер AndroidX). Сви тестови се покрећу на емулатору или физичком уређају кроз Android Test Orchestrator.
// build.gradle.kts (androidTest dependencies)
android {
defaultConfig {
testInstrumentationRunner =
"androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.6.1")
androidTestImplementation("androidx.test:runner:1.6.1")
androidTestImplementation("androidx.test:rules:1.6.1")
}
Espresso тестови се могу покретати кроз Google Android Test Orchestrator, који изолује сваки тест у посебан процес и чисти стање између покретања. Ово елиминише flaky тестове повезане са преосталим подацима претходних тестова и повећава стабилност на CI серверима. За паралелно покретање користи се sharding — расподела тестова између више емулатора.
Често постављана питања
Espresso ради унутар процеса апликације и користи аутоматску синхронизацију са UI током. UI Automator ради на нивоу система, може интераговати са туђим апликацијама, али захтева ручно управљање чекањем.
Ово је метафора из Google презентације: тест Espresso стоји на три ослонца — ViewMatcher (претрага), ViewAction(акција) и ViewAssertion(провера). Ако се уклони било који од њих, тест губи стабилност, попут треногог пса.
За RecyclerView се користи библиотека espresso-contrib и методе onView(withId(R.id.recycler)).perform(actionOnItemAtPosition(0, click())). Алтернатива — onData() за AdapterView или прилагођени ViewAction за претрагу елемента по тексту унутар RecyclerView-а. Додатно се може користити RecyclerViewActions из espresso-contrib-а за скроловање до елемента и акције над њим.
Flaky тест је тест који понекад пада без промене кода, због race condition-а или асинхроности. Espresso решава овај проблем кроз Idling Resource — чекање завршетка свих позадинских задатака пре извршења провере.
Сам Espresso није намењен за скриншот тестове, али се може комбиновати са библиотекама попут Shot или Paparazzi. Espresso припрема UI у жељено стање, а библиотека за поређење прави скриншот и упоређује га са референцом. Овај приступ се назива визуелно регресионо тестирање и помаже у проналажењу неочекиваних промена у интерфејсу.
Завршне напомене
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође