Espresso è un framework per test automatici dell'interfaccia utente di applicazioni Android, sviluppato dal team di Google e incluso in AndroidX Test. A differenza dei test strumentali che verificano componenti isolati, Espresso interagisce con l'interfaccia reale: clicca pulsanti, inserisce testo, verifica la visualizzazione degli elementi. Secondo Google Android Developers, Espresso fornisce sincronizzazione automatica con il thread dell'interfaccia utente, eliminando la necessità di Thread.sleep() manuali.
Punti chiave
Espresso è una libreria per scrivere test automatici dell'interfaccia utente per Android, parte di Google AndroidX Test. Fornisce un'API per trovare elementi View sullo schermo, eseguire azioni su di essi (clic, inserimento, swipe) e verificarne lo stato (visualizzato, contiene testo, abilitato).
La caratteristica principale di Espresso è la sincronizzazione automatica con il thread principale dell'applicazione. Il framework attende il completamento di tutte le attività asincrone (coroutine, AsyncTask, Handler) prima di eseguire il controllo successivo. Questo elimina i test instabili causati da condizioni di competizione e rende i test dell'interfaccia stabili e affidabili — nessun test contiene Thread.sleep() o cicli di attesa.
Espresso segue il principio del Cane a Tre Zampe — un test consiste in tre passaggi: trovare un elemento (ViewMatcher), eseguire un'azione (ViewAction), verificare il risultato (ViewAssertion). Tutti e tre i passaggi sono scritti in una catena di chiamate: onView().perform().check(). Questo concetto rende i test prevedibili e facili da leggere — ogni test descrive esplicitamente cosa cerca, cosa fa e cosa verifica.
Architettura di Espresso si basa su tre componenti: Espresso (punto di ingresso — metodi statici onView e onData), ViewMatchers (ricerca elementi), ViewActions (azioni) e ViewAssertions (verifiche). Internamente, il framework utilizza Idling Resource per la sincronizzazione con il thread dell'interfaccia.
Il test più semplice trova un pulsante per ID, esegue un clic e verifica che appaia il testo “Completato”. Tutte le operazioni sono sincrone dal punto di vista del test — Espresso garantisce che il thread dell'interfaccia abbia terminato l'elaborazione dell'evento prima che il test continui. Ciò è ottenuto tramite un meccanismo di attesa integrato: onView blocca l'esecuzione del test fino a quando l'interfaccia non diventa inattiva.
@Test
fun buttonClick_showsSuccessText() {
// Trova pulsante per ID e clicca
onView(withId(R.id.button_submit))
.perform(click())
// Verifica che il testo “Completato” sia visualizzato
onView(withText("Completato"))
.check(matches(isDisplayed()))
}
Per avviare un test Espresso, si utilizza ActivityScenario (AndroidX Test), che crea un'Activity in uno stato specifico — in esecuzione, in pausa o distrutta. ActivityScenario consente di testare il ciclo di vita dell'Activity oltre ai test dell'interfaccia puri. Ad esempio, si può verificare che i dati siano preservati durante la rotazione dello schermo (ricreazione dell'Activity) e ripristinati dopo la distruzione.
ViewMatchers è un insieme di metodi della classe Espresso.onView che consentono di trovare Views sullo schermo secondo vari criteri: ID risorsa (R.id), testo, suggerimento, elemento padre e gerarchia. Se un matcher non produce un risultato univoco, i matcher possono essere combinati usando allOf().
| Matcher | Scopo |
|---|---|
| withId(R.id.name) | Ricerca per ID risorsa |
| withText(“testo”) | Ricerca per testo visualizzato |
| withHint(“suggerimento”) | Ricerca per attributo hint di EditText |
| isDisplayed() | Verifica che l'elemento sia visibile sullo schermo |
| hasSibling(matcher) | Ricerca per elemento fratello |
| allOf(m1, m2) | Combinazione di più matcher |
Se ci sono più elementi identici sullo schermo (ad esempio, due TextView con testo diverso), è conveniente combinare i matcher usando allOf: onView(allOf(withId(R.id.title), withText(“Ciao”))). Ciò garantisce la selezione di un singolo elemento. L'operatore inverso — not() — esclude elementi dalla ricerca, e hasSibling() trova un elemento accanto a uno noto.
ViewActions sono azioni che Espresso esegue sulla View trovata: click(), typeText(), clearText(), scrollTo(), swipeLeft() e altre. Le azioni vengono passate al metodo perform(), che può accettare più azioni in sequenza.
Il metodo perform() accetta vararg ViewAction, consentendo di eseguire una sequenza di azioni su un elemento: cancellare il campo, inserire nuovo testo, chiudere la tastiera e cliccare un pulsante. Tutte le azioni vengono eseguite nell'ordine in cui sono elencate, e Espresso garantisce che l'azione precedente sia completata prima che inizi la successiva.
// Inserisci testo in EditText e clicca pulsante
onView(withId(R.id.edit_email))
.perform(
clearText(),
typeText("user@example.com"),
closeSoftKeyboard()
)
onView(withId(R.id.button_login))
.perform(click())
Per gli elementi all'interno di AdapterView (ListView, RecyclerView), si utilizza il metodo onData() invece di onView. Lavora con i dati dell'adattatore anziché con le Views — trova un elemento per contenuto del modello e restituisce la View corrispondente per azioni successive. onData utilizza matcher hamcrest per localizzare un elemento tramite i campi dei dati del modello.
ViewAssertions verificano che una View sia in uno stato specifico. Il metodo base — matches(matcher) — verifica che l'elemento corrisponda al matcher fornito. Inoltre, Espresso offre doesNotExist() (elemento assente) e selectedDescendantsMatch() (verifica di elementi annidati).
Le verifiche più frequenti nei test dell'interfaccia: elemento visualizzato (isDisplayed), elemento contiene testo specifico (withText), elemento abilitato (isEnabled), elemento non selezionato (isNotChecked). Ogni verifica lancia un'eccezione dettagliata in caso di fallimento — inclusa la gerarchia delle Views sullo schermo. Ciò semplifica il debugging: il messaggio di errore mostra quali elementi erano effettivamente sullo schermo al momento della verifica.
Se le verifiche standard sono insufficienti, è possibile crearne una personalizzata tramite l'interfaccia ViewAssertion. Un'asserzione personalizzata riceve una View e può verificarne lo stato a livello di codice — ad esempio, colore del testo, padding o lo stato di un componente personalizzato non esposto tramite matcher standard.
// Verifica: TextView visualizzato e contiene testo
onView(withId(R.id.text_welcome))
.check(matches(isDisplayed()))
.check(matches(withText("Benvenuto")))
// Verifica: elemento NON visualizzato
onView(withId(R.id.progress_bar))
.check(doesNotExist())
Idling Resource è il meccanismo di Espresso per sincronizzare il test con operazioni asincrone. Per impostazione predefinita, Espresso attende Handler, AsyncTask e coroutine (tramite coroutinesIdlingResource). Se l'applicazione esegue lavoro in background tramite thread personalizzati o servizi di callback, è necessario registrare un Idling Resource personalizzato.
A partire da AndroidX Test 1.4.0, Espresso supporta le coroutine tramite CoroutinesIdlingResource. Il test attende automaticamente il completamento di tutte le coroutine avviate prima di eseguire verifiche dell'interfaccia. Per scenari più complessi, viene utilizzato CountingIdlingResource — un contatore che si incrementa all'avvio di un'attività e si decrementa al completamento.
// Registra IdlingResource per 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
}
}
Integrazione di Espresso in un progetto Android si effettua aggiungendo dipendenze nel build.gradle a livello di modulo. Espresso fa parte di AndroidX Test, quindi è sufficiente specificare le dipendenze per il core di Espresso, le estensioni e l'integrazione JUnit. I test vengono inseriti nella directory src/androidTest ed eseguiti su un dispositivo fisico o emulatore tramite AndroidJUnitRunner.
Il set minimo di dipendenze include espresso-core (core), espresso-contrib (matcher aggiuntivi per RecyclerView, Drawer, Picker) e runner (esecutore di test AndroidX). Tutti i test vengono eseguiti su un emulatore o dispositivo fisico tramite Android Test Orchestrator.
// build.gradle.kts (dipendenze androidTest)
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")
}
I test Espresso possono essere eseguiti tramite Google Android Test Orchestrator, che isola ogni test in un processo separato e pulisce lo stato tra le esecuzioni. Ciò elimina i test instabili causati da dati residui di test precedenti e migliora la stabilità sui server CI. Per l'esecuzione parallela, viene utilizzato lo sharding — distribuzione dei test tra più emulatori.
Domande frequenti
Espresso funziona all'interno del processo dell'applicazione e utilizza la sincronizzazione automatica con il thread dell'interfaccia. UI Automator funziona a livello di sistema, può interagire con altre applicazioni, ma richiede gestione manuale dell'attesa.
È una metafora di una presentazione Google: un test Espresso si regge su tre pilastri — ViewMatcher (cercare), ViewAction(agire) e ViewAssertion(verificare). Rimuoverne uno rende il test instabile, come un cane a tre zampe.
Per RecyclerView si utilizza la libreria espresso-contrib e metodi come onView(withId(R.id.recycler)).perform(actionOnItemAtPosition(0, click())). Un'alternativa è onData() per AdapterView o un ViewAction personalizzato per trovare un elemento per testo all'interno di RecyclerView. Inoltre, RecyclerViewActions da espresso-contrib può essere utilizzato per scorrere fino a un elemento ed eseguire azioni su di esso.
Un test instabile è un test che talvolta fallisce senza modifiche al codice, a causa di condizioni di competizione o asincronicità. Espresso risolve questo problema usando Idling Resource — attendere il completamento di tutte le attività in background prima di eseguire le verifiche.
Espresso stesso non è progettato per test di screenshot, ma può essere combinato con librerie come Shot o Paparazzi. Espresso prepara l'interfaccia nello stato desiderato, e la libreria di confronto scatta uno screenshot e lo confronta con un riferimento. Questo approccio è chiamato test di regressione visiva e aiuta a trovare cambiamenti inaspettati nell'interfaccia.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche