Questo documento spiega come completare le attività di test automatiche più comuni utilizzando l'API Espresso.
L'API Espresso incoraggia gli autori di test a pensare in termini di cosa un utente potrebbe fare mentre interagisce con l'applicazione, individuando gli elementi dell'interfaccia utente e interagendo con loro. Allo stesso tempo, il framework impedisce l'accesso diretto alle attività e alle visualizzazioni dell'applicazione perché mantenere questi oggetti e utilizzarli dal thread dell'interfaccia utente è una delle principali fonti di problemi di test. Pertanto, non vedrai metodi come getView()
e getCurrentActivity()
nell'API Espresso.
Puoi comunque eseguire in sicurezza le viste implementando le tue sottoclassi ViewAction
e ViewAssertion
.
Componenti API
I componenti principali di Espresso sono i seguenti:
- Espresso: punto di ingresso per le interazioni con le viste (tramite
onView()
eonData()
). Espone anche le API che non sono necessariamente legate a una vista, ad esempiopressBack()
. - ViewMatchers: una raccolta di oggetti che implementano l'interfaccia di
Matcher<? super View>
. Puoi passare uno o più di questi elementi al metodoonView()
per individuare una vista all'interno della gerarchia di visualizzazione corrente. - ViewActions: una raccolta di
ViewAction
oggetti che possono essere trasmessi al metodoViewInteraction.perform()
, ad esempioclick()
. - ViewAssertions: una raccolta di
ViewAssertion
oggetti che possono essere passati al metodoViewInteraction.check()
. La maggior parte delle volte utilizzerai l'asserzione Corrispondenze, che utilizza un matcher Visualizza per rivendicare lo stato della 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()
prende in considerazione un matcher hamcrest che corrisponde a una sola visualizzazione all'interno della gerarchia di visualizzazioni corrente. I matcher sono potenti e saranno familiari a chi li ha utilizzati con Mockito o JUnit. Se non hai dimestichezza con gli abbinamenti hamcrest, ti suggeriamo di iniziare dando un'occhiata veloce a questa presentazione.
Spesso la visualizzazione desiderata ha un valore R.id
univoco e un semplice matcher withId
restringe la ricerca della visualizzazione. Tuttavia, in molti casi legittimi non è possibile determinare R.id
durante lo sviluppo del test. Ad esempio, la vista specifica potrebbe non avere un R.id
o R.id
non è univoco. Ciò può rendere i normali test di strumentazione fragili e complicati da scrivere, poiché il modo normale per accedere alla vista, con findViewById()
, non funziona. Di conseguenza, potresti dover accedere ai membri privati dell'attività o del frammento che contengono la vista o trovare un container con un R.id
noto e accedere ai suoi contenuti per la visualizzazione specifica.
Espresso gestisce questo problema in modo pulito, consentendoti di restringere la visualizzazione utilizzando oggetti ViewMatcher
esistenti o personalizzati.
Trovare una vista 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 R.id
sono condivisi tra più viste. In questo caso, il tentativo di utilizzare un determinato R.id
genera un'eccezione, ad esempio AmbiguousViewMatcherException
. Il messaggio di eccezione fornisce una rappresentazione testuale della gerarchia corrente delle viste, in cui puoi cercare e trovare le viste che corrispondono al valore R.id
non univoco:
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****
Esaminando i vari attributi delle viste, potresti
trovare proprietà identificabili in modo univoco. Nell'esempio precedente, una delle visualizzazioni ha il testo
"Hello!"
. Puoi utilizzare questa opzione per restringere la ricerca utilizzando
associatori di combinazioni:
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"))));
Consulta la pagina ViewMatchers
per i matcher delle visualizzazioni forniti da Espresso.
considerazioni
- In un'applicazione corretta, tutte le viste con cui un utente può interagire
devono contenere testo descrittivo o avere una descrizione dei contenuti. Consulta
Rendere più accessibili le app per ulteriori
dettagli. Se non riesci a restringere una ricerca utilizzando
withText()
owithContentDescription()
, valuta la possibilità di considerarla come un bug di accessibilità. - Utilizza il matcher meno descrittivo che trova la visualizzazione che stai cercando. Non specificare troppo, in quanto questo costringerà il framework a svolgere più lavoro del necessario. Ad esempio, se una vista è identificabile in modo univoco dal suo testo, non è necessario specificare che può essere assegnata anche da
TextView
. Per molte visualizzazioni, il valoreR.id
della visualizzazione dovrebbe essere sufficiente. - Se la vista di destinazione si trova all'interno di un elemento
AdapterView
, comeListView
,GridView
oSpinner
, il metodoonView()
potrebbe non funzionare. In questi casi, devi utilizzare inveceonData()
.
Eseguire un'azione su una vista
Dopo aver trovato un matcher adatto per la vista di destinazione, è possibile eseguire istanze di ViewAction
su di esso utilizzando il metodo perform.
Ad esempio, per fare clic sulla visualizzazione:
Kotlin
onView(...).perform(click())
Java
onView(...).perform(click());
Puoi eseguire più di un'azione con una chiamata Esegui:
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 elemento ScrollView
(verticale o orizzontale), valuta le azioni precedenti che richiedono la visualizzazione della vista, ad esempio click()
e typeText()
, con scrollTo()
. In questo modo viene garantita la visualizzazione della visualizzazione prima di procedere con l'altra azione:
Kotlin
onView(...).perform(scrollTo(), click())
Java
onView(...).perform(scrollTo(), click());
Consulta la sezione ViewActions
per le azioni di visualizzazione fornite da Espresso.
Seleziona Visualizza asserzioni
Le asserzioni possono essere applicate alla vista attualmente selezionata con il metodo check()
. L'asserzione più utilizzata è matches()
. Utilizza un oggetto ViewMatcher
per dichiarare lo stato della vista attualmente selezionata.
Ad esempio, per verificare che una vista abbia il testo "Hello!"
:
Kotlin
onView(...).check(matches(withText("Hello!")))
Java
onView(...).check(matches(withText("Hello!")));
Per affermare che "Hello!"
è il contenuto della visualizzazione, la seguente è considerata 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()));
D'altra parte, se vuoi dichiarare che una vista con il testo "Hello!"
è visibile, ad esempio dopo una modifica del flag di visibilità delle viste, il codice è corretto.
Visualizza il test semplice dell'asserzione
In questo esempio, SimpleActivity
contiene Button
e TextView
. Quando
fai clic sul pulsante, il contenuto di TextView
diventa "Hello Espresso!"
.
Ecco come eseguire un test con Espresso:
Fai clic sul pulsante
Il primo passaggio è cercare una proprietà che aiuti a trovare il pulsante. Il pulsante 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, per eseguire il clic:
Kotlin
onView(withId(R.id.button_simple)).perform(click())
Java
onView(withId(R.id.button_simple)).perform(click());
Verificare il testo TextView
Anche il TextView
con il testo da verificare ha un R.id
univoco:
Kotlin
onView(withId(R.id.text_simple))
Java
onView(withId(R.id.text_simple));
Ora, per 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!")));
Controlla il caricamento dei dati nelle visualizzazioni adattatore
AdapterView
è un tipo speciale di widget che carica i dati in modo dinamico da un adattatore. L'esempio più comune di AdapterView
è ListView
. Diversamente dai widget statici come LinearLayout
, nella gerarchia di visualizzazioni corrente può essere caricato solo un sottoinsieme dei AdapterView
elementi secondari. Una semplice ricerca onView()
non troverà le viste attualmente non caricate.
Espresso gestisce questo problema fornendo un punto di ingresso onData()
separato che consente di caricare innanzitutto l'elemento dell'adattatore in questione, mettendolo a fuoco prima di utilizzare l'adattatore o uno dei suoi dispositivi secondari.
Avviso: le implementazioni personalizzate di AdapterView
possono avere problemi con il metodo onData()
se violano i contratti di ereditarietà, in particolare l'API getItem()
. In questi casi, la cosa migliore da fare
è il refactoring del codice dell'applicazione. In caso contrario, puoi implementare un
AdapterViewProtocol
personalizzato corrispondente. Per ulteriori informazioni, dai un'occhiata alla classe
AdapterViewProtocols
predefinita fornita da Espresso.
Test semplice della visualizzazione adattatore
Questo semplice test dimostra come utilizzare onData()
. SimpleActivity
contiene Spinner
con alcuni elementi che rappresentano determinati tipi di bevande a base di caffè. Quando
è selezionato un elemento, esiste un elemento TextView
che cambia in "One %s a day!"
, dove
%s
rappresenta l'elemento selezionato.
L'obiettivo di questo test è aprire il Spinner
, selezionare un articolo specifico e
verificare che il TextView
contenga l'articolo. Poiché la classe Spinner
si basa su AdapterView
, è consigliabile utilizzare onData()
anziché onView()
per la corrispondenza dell'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 dell'elemento, l'elemento Spinner
crea un elemento ListView
con i relativi contenuti.
Questa visualizzazione può essere molto lunga e l'elemento potrebbe non essere contribuito alla gerarchia delle visualizzazioni. Con onData()
forziamo l'elemento desiderato nella gerarchia delle visualizzazioni. Gli elementi in Spinner
sono stringhe, quindi vogliamo trovare una corrispondenza per 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 informazioni di debug utili quando un test ha esito negativo:
Logging
Espresso registra tutte le azioni di visualizzazione in logcat. Ecco alcuni esempi:
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 visualizzazione di destinazione, viene lanciato unNoMatchingViewException
. Puoi esaminare la gerarchia delle viste nella stringa dell'eccezione per analizzare il motivo per cui il matcher non corrisponde ad alcuna vista. - Se
onView()
trova più visualizzazioni che corrispondono a un determinato matcher, viene lanciato unAmbiguousViewMatcherException
. La gerarchia delle visualizzazioni viene stampata e tutte le visualizzazioni corrispondenti sono contrassegnate dall'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****
In caso di una gerarchia di visualizzazioni complicata o di comportamenti imprevisti dei widget, è sempre utile utilizzare il visualizzatore gerarchia in Android Studio per avere una spiegazione.
Avvisi relativi alla visualizzazione dell'adattatore
Espresso avvisa gli utenti della presenza di AdapterView
widget. Quando un'operazione onView()
genera un widget NoMatchingViewException
e AdapterView
sono presenti nella gerarchia delle visualizzazioni, la soluzione più comune è utilizzare onData()
.
Il messaggio di eccezione include un avviso con un elenco delle visualizzazioni dell'adattatore.
Puoi utilizzare queste informazioni per richiamare onData()
e caricare la visualizzazione di destinazione.
Risorse aggiuntive
Per ulteriori informazioni sull'utilizzo di Espresso nei test Android, consulta le risorse seguenti.
Samples
- CustomMatcherSample:
mostra come estendere Espresso in modo che corrisponda alla proprietà hint di un oggetto
EditText
. - RecyclerViewSample:
RecyclerView
azioni per Espresso. - (altro...)