Nozioni di base su espresso

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() e onData()). Espone anche le API che non sono necessariamente legate a nessuna vista, come come pressBack().
  • 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 metodo ViewInteraction.perform(), ad esempio click().
  • ViewAssertions: una raccolta di ViewAssertion oggetti che possono essere ha superato il metodo ViewInteraction.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() o withContentDescription(), 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, il R.id della vista dovrebbe essere sufficiente.
  • Se la vista target si trova all'interno di un valore AdapterView, ad esempio ListView, GridView o Spinner: il metodo onView() potrebbe non funzionare. In queste casi, devi usare invece onData().

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 un NoMatchingViewException 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 lanciato AmbiguousViewMatcherException. La gerarchia delle visualizzazioni viene stampata visualizzazioni corrispondenti sono contrassegnate con l'etichetta MATCHES:
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