Questo documento spiega come completare le attività di test automatici più comuni utilizzando il API Espresso.
L'API Espresso incoraggia gli autori dei test a pensare in termini di ciò che un utente potrebbe
fare durante l'interazione con l'applicazione: individuare gli elementi dell'interfaccia utente e interagire
con loro. Allo stesso tempo, il framework impedisce l'accesso diretto alle attività
e viste dell'applicazione perché conserva questi oggetti e il funzionamento
fuori dal thread della UI è una delle principali fonti di malessere nei test. Di conseguenza,
non vedrai metodi come getView()
e getCurrentActivity()
nell'API Espresso.
Puoi comunque operare in sicurezza sulle viste implementando le tue sottoclassi di
ViewAction
e ViewAssertion
.
Componenti dell'API
I componenti principali di Espresso includono:
- Espresso: punto di contatto per le interazioni con le visualizzazioni (tramite
onView()
eonData()
). Espone anche le API che non sono necessariamente legate a nessuna vista, come comepressBack()
. - ViewMatchers: una raccolta di oggetti che implementano il parametro
Matcher<? super View>
. Puoi passarne uno o piùonView()
per individuare una vista all'interno della gerarchia delle visualizzazioni corrente. - ViewAzioni: una raccolta di
ViewAction
oggetti che possono essere passati a il metodoViewInteraction.perform()
, ad esempioclick()
. - ViewAssertions: una raccolta di
ViewAssertion
oggetti che possono essere ha superato il metodoViewInteraction.check()
. Nella maggior parte dei casi, utilizzerai corrisponde all'asserzione, che utilizza un matcher View per dichiarare lo stato del vista attualmente selezionata.
Esempio:
Kotlin
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()))
Java
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()));
Trovare una visualizzazione
Nella stragrande maggioranza dei casi, il metodo onView()
utilizza un ricercatore di incidenti
che dovrebbe corrispondere a una (e una sola) visualizzazione nella visualizzazione corrente
nella gerarchia. I matcher sono molto potenti e risulteranno familiari a coloro che hanno usato
con Mockito o JUnit. Se non hai familiarità con questi abbinamenti,
di iniziare con una rapida occhiata a questo
presentazione.
Spesso la vista desiderata ha un valore R.id
univoco e un semplice matcher withId
restringere la ricerca delle visualizzazioni. Tuttavia, esistono molti casi legittimi in cui
impossibile determinare R.id
in fase di sviluppo del test. Ad esempio, la vista specifica
potrebbe non avere un valore R.id
oppure R.id
non è univoco. Questo può rendere normale
i test di strumentazione sono fragili e complicati da scrivere perché il modo normale
l'accesso alla vista, con findViewById()
, non funziona. Pertanto, puoi
devi accedere ai membri privati dell'Attività o del Frammento che contengono la vista oppure
trovare un container con un valore R.id
noto e accedere ai relativi contenuti per
vista specifica.
Espresso gestisce questo problema in modo pulito consentendoti di restringere la visualizzazione
utilizzando gli oggetti ViewMatcher
esistenti o i tuoi oggetti personalizzati.
Per trovare una visualizzazione in base al suo R.id
è semplice come chiamare onView()
:
Kotlin
onView(withId(R.id.my_view))
Java
onView(withId(R.id.my_view));
A volte, i valori di R.id
vengono condivisi tra più viste. In questi casi,
tentare di utilizzare un determinato R.id
fa un'eccezione, come
AmbiguousViewMatcherException
. Il messaggio di eccezione fornisce un SMS
rappresentazione della gerarchia di visualizzazione corrente, che puoi cercare e trovare
le viste che corrispondono alla metrica R.id
non univoca:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
Osservando le varie caratteristiche delle visualizzazioni, potresti trovare informazioni uniche
e le proprietà identificabili. Nell'esempio precedente, una delle viste ha il testo
"Hello!"
. Puoi utilizzare questa opzione per restringere la ricerca utilizzando una combinazione
matcher:
Kotlin
onView(allOf(withId(R.id.my_view), withText("Hello!")))
Java
onView(allOf(withId(R.id.my_view), withText("Hello!")));
Puoi anche scegliere di non invertire nessuno dei matcher:
Kotlin
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
Java
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));
Vedi ViewMatchers
per i matcher delle visualizzazioni forniti da Espresso.
Considerazioni
- In un'applicazione ben gestita, tutte le visualizzazioni con cui un utente può interagire
devono contenere un testo descrittivo o una descrizione dei contenuti. Consulta
Rendere le app più accessibili per un pubblico più vasto
i dettagli. Se non riesci a restringere una ricerca utilizzando
withText()
owithContentDescription()
, consideralo un bug di accessibilità. - Utilizza il matcher meno descrittivo che trova la visualizzazione che cerchi
. Non specificare troppe informazioni perché costringerà il framework a svolgere un lavoro maggiore rispetto a
necessaria. Ad esempio, se una visualizzazione è identificabile in modo univoco dal suo testo,
non è necessario specificare che la vista è assegnabile anche da
TextView
. Per molti visualizzazioni, ilR.id
della vista dovrebbe essere sufficiente. - Se la vista target si trova all'interno di un valore
AdapterView
, ad esempioListView
,GridView
oSpinner
: il metodoonView()
potrebbe non funzionare. In queste casi, devi usare inveceonData()
.
Eseguire un'azione su una visualizzazione
Dopo aver trovato un matcher adatto alla vista target, è possibile:
di eseguire istanze di ViewAction
utilizzando il metodo perform.
Ad esempio, per fare clic sulla vista:
Kotlin
onView(...).perform(click())
Java
onView(...).perform(click());
Puoi eseguire più di un'azione con una singola chiamata di esecuzione:
Kotlin
onView(...).perform(typeText("Hello"), click())
Java
onView(...).perform(typeText("Hello"), click());
Se la vista su cui stai lavorando si trova all'interno di un campo ScrollView
(verticale o
orizzontale), prendi in considerazione le azioni precedenti che richiedono che la visualizzazione sia
visualizzati, ad esempio click()
e typeText()
, con scrollTo()
. Questo
assicura che la visualizzazione venga mostrata prima di procedere con l'altra azione:
Kotlin
onView(...).perform(scrollTo(), click())
Java
onView(...).perform(scrollTo(), click());
Vedi ViewActions
per le azioni di visualizzazione fornite da Espresso.
Controlla visualizza asserzioni
Le asserzioni possono essere applicate alla vista attualmente selezionata con l'check()
. L'asserzione più utilizzata è matches()
. Utilizza un
ViewMatcher
per rivendicare lo stato della vista attualmente selezionata.
Ad esempio, per verificare che una visualizzazione abbia il testo "Hello!"
:
Kotlin
onView(...).check(matches(withText("Hello!")))
Java
onView(...).check(matches(withText("Hello!")));
Se vuoi affermare che "Hello!"
è un contenuto della vista, quanto segue è considerato una prassi scorretta:
Kotlin
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
Java
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
Se invece vuoi affermare che una visualizzazione con il testo "Hello!"
è
visibile, ad esempio dopo una modifica del flag di visibilità delle viste,
codice va bene.
Visualizza test semplice dell'asserzione
In questo esempio, SimpleActivity
contiene un Button
e un TextView
. Quando
viene fatto clic sul pulsante, i contenuti di TextView
diventano "Hello Espresso!"
.
Ecco come fare un test con Espresso:
Fai clic sul pulsante
Il primo passaggio consiste nel cercare una proprietà che aiuti a trovare il pulsante. La
in SimpleActivity
ha un valore R.id
univoco, come previsto.
Kotlin
onView(withId(R.id.button_simple))
Java
onView(withId(R.id.button_simple));
A questo punto, devi eseguire il clic:
Kotlin
onView(withId(R.id.button_simple)).perform(click())
Java
onView(withId(R.id.button_simple)).perform(click());
Verifica il testo TextView
Il TextView
con il testo da verificare ha anche un R.id
univoco:
Kotlin
onView(withId(R.id.text_simple))
Java
onView(withId(R.id.text_simple));
Ora devi verificare il testo dei contenuti:
Kotlin
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
Java
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
Controllare il caricamento dei dati nelle viste adattatori
AdapterView
è un tipo speciale di widget che carica i dati in modo dinamico da
un adattatore. L'esempio più comune di AdapterView
è ListView
. Come
al contrario di widget statici come LinearLayout
, solo un sottoinsieme dei
Potrebbero essere caricati AdapterView
elementi secondari nella gerarchia di visualizzazione corrente. Un semplice
La ricerca di onView()
non ha trovato le visualizzazioni attualmente non caricate.
Espresso gestisce questa situazione fornendo un punto di ingresso onData()
separato, che
in grado di caricare per la prima volta l'elemento dell'adattatore in questione, mettendolo a fuoco prima di
operazioni su di esso o su uno dei suoi figli.
Avviso: implementazioni personalizzate di
AdapterView
può avere problemi con onData()
se interrompono i contratti di ereditarietà, in particolare il
API getItem()
. In questi casi, la linea d'azione migliore è
eseguire il refactoring del codice dell'applicazione. Se non puoi farlo, puoi implementare una
corrispondente a AdapterViewProtocol
personalizzato. Per ulteriori informazioni, consulta
guarda il valore predefinito
Corso AdapterViewProtocols
fornito da Espresso.
Test semplice della visualizzazione dell'adattatore
Questo semplice test dimostra come utilizzare onData()
. SimpleActivity
contiene un
Spinner
con alcuni elementi che rappresentano i tipi di bevande al caffè. Quando
elemento selezionato, c'è un TextView
che diventa "One %s a day!"
, dove
%s
rappresenta l'elemento selezionato.
L'obiettivo di questo test è aprire l'Spinner
, selezionare un elemento specifico e
verifica che TextView
contenga l'elemento. Poiché il corso Spinner
è basato
suAdapterView
, è consigliabile utilizzare onData()
anziché onView()
per
corrispondenti all'elemento.
Apri la selezione dell'elemento
Kotlin
onView(withId(R.id.spinner_simple)).perform(click())
Java
onView(withId(R.id.spinner_simple)).perform(click());
Selezionare un elemento
Per la selezione degli elementi, Spinner
crea un ListView
con i suoi contenuti.
Questa visualizzazione può essere molto lunga e l'elemento potrebbe non essere contribuito alla visualizzazione
nella gerarchia. Utilizzando onData()
, forza la visualizzazione dell'elemento desiderato
nella gerarchia. Gli elementi in Spinner
sono stringhe, quindi vogliamo far corrispondere un elemento
uguale alla stringa "Americano"
:
Kotlin
onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))).perform(click())
Java
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
Verifica che il testo sia corretto
Kotlin
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))))
Java
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
Debug…
Espresso fornisce utili informazioni di debug quando un test non va a buon fine:
Logging
Espresso registra tutte le azioni di visualizzazione in logcat. Ad esempio:
ViewInteraction: Performing 'single click' action on view with text: Espresso
Visualizza gerarchia
Espresso stampa la gerarchia delle visualizzazioni nel messaggio di eccezione quando onView()
non riesce.
- Se
onView()
non trova la vista target, viene restituito unNoMatchingViewException
lanciate. Puoi esaminare la gerarchia delle visualizzazioni nella stringa delle eccezioni per analizzare il motivo per cui il matcher non corrisponde ad alcuna visualizzazione. - Se
onView()
trova più visualizzazioni corrispondenti a un determinato matcher, viene Viene lanciatoAmbiguousViewMatcherException
. La gerarchia delle visualizzazioni viene stampata visualizzazioni corrispondenti sono contrassegnate con l'etichettaMATCHES
:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
Quando si ha a che fare con una gerarchia di visualizzazione complicata o un comportamento imprevisto dei widget è sempre utile utilizzare Visualizzatore gerarchia in Android Studio per una spiegazione.
Avvisi di visualizzazione dell'adattatore
Espresso avvisa gli utenti della presenza di widget AdapterView
. Quando onView()
l'operazione genera un widget NoMatchingViewException
e AdapterView
presente nella gerarchia delle visualizzazioni, la soluzione più comune è utilizzare onData()
.
Il messaggio di eccezione includerà un avviso con un elenco di viste adattatori.
Puoi utilizzare queste informazioni per richiamare onData()
e caricare la vista di destinazione.
Risorse aggiuntive
Per ulteriori informazioni sull'uso di Espresso nei test Android, consulta le seguenti risorse.
Campioni
- CustomMatcherSample:
Mostra come estendere Espresso in modo che corrisponda alla proprietà hint di un oggetto
EditText
. - Esempio di visualizzazione riciclo:
RecyclerView
azioni per Espresso. - (altro...)