Test delle interfacce utente Android con Espresso

In questo post imparerai come scrivere i test dell'interfaccia utente con il framework di test Espresso e automatizzare il tuo flusso di lavoro di test, invece di utilizzare il noioso e altamente soggettivo processo di errore. 

Espresso è un framework di test per la scrittura di test dell'interfaccia utente in Android. Secondo i documenti ufficiali, puoi:

Usa Espresso per scrivere test dell'interfaccia utente Android concisi, belli e affidabili.

1. Perché usare Espresso?

Uno dei problemi con i test manuali è che può richiedere molto tempo e noioso. Ad esempio, per testare una schermata di accesso (manualmente) in un'app per Android, dovrai fare quanto segue:

  1. Avvia l'app. 
  2. Passare alla schermata di accesso. 
  3. Conferma se il usernameEditText e passwordEditText sono visibili. 
  4. Digita il nome utente e la password nei rispettivi campi. 
  5. Conferma se il pulsante di accesso è visibile, quindi fai clic sul pulsante di accesso.
  6. Controllare se vengono visualizzate le visualizzazioni corrette quando l'accesso è andato a buon fine o si è verificato un errore. 

Invece di spendere tutto questo tempo a testare manualmente la nostra app, sarebbe meglio dedicare più tempo alla scrittura del codice che rende la nostra app distinguibile dal resto! E, anche se il test manuale è noioso e piuttosto lento, è ancora soggetto a errori e potresti perdere alcuni casi d'angolo. 

Alcuni dei vantaggi dei test automatici includono quanto segue:   

  • I test automatici eseguono esattamente gli stessi casi di test ogni volta che vengono eseguiti. 
  • Gli sviluppatori possono individuare rapidamente un problema rapidamente prima di inviarlo al team addetto al controllo qualità. 
  • Può risparmiare molto tempo, a differenza dei test manuali. Risparmiando tempo, i tecnici del software e il team addetto al controllo qualità possono invece dedicare più tempo a compiti impegnativi e gratificanti. 
  • Viene raggiunta una copertura di prova più elevata, che porta a un'applicazione di qualità migliore. 

In questo tutorial, impareremo su Espresso integrandolo in un progetto Android Studio. Scriveremo i test dell'interfaccia utente per una schermata di accesso e a RecyclerView, e impareremo a testare gli intenti. 

La qualità non è un atto, è un'abitudine. - Pablo Picasso

2. Prerequisiti

Per poter seguire questo tutorial, avrai bisogno di:

  • una conoscenza di base delle principali API Android e Kotlin
  • Android Studio 3.1.3 o versioni successive
  • Plugin Kotlin 1.2.51 o superiore

Un esempio di progetto (in Kotlin) per questo tutorial può essere trovato sul nostro repository GitHub in modo da poter seguire facilmente.

3. Creare un progetto per Android Studio

Avvia il tuo Android Studio 3 e crea un nuovo progetto con un'attività vuota chiamata Attività principale. Assicurati di controllare Include il supporto Kotlin

4. Impostare Espresso e AndroidJUnitRunner

Dopo aver creato un nuovo progetto, assicurati di aggiungere le seguenti dipendenze dalla libreria di supporto test di Android nel tuo build.gradle (sebbene Android Studio li abbia già inclusi per noi). In questo tutorial, stiamo usando l'ultima versione 3.0.2 della libreria Espresso (al momento della stesura di questo libro). 

android // ... defaultConfig // ... testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // ... dipendenze // ... androidTestImplementation 'com.android.support.test.espresso: espresso-core: 3.0. 2 'androidTestImplementation' com.android.support.test: runner: 1.0.2 'androidTestImplementation' com.android.support.test: rules: 1.0.2 '

Abbiamo incluso anche il corridore della strumentazione AndroidJUnitRunner:

Un Strumentazione che esegue i test JUnit3 e JUnit4 su un pacchetto Android (applicazione).

Nota che Strumentazione è semplicemente una classe base per l'implementazione del codice di strumentazione dell'applicazione. 

Disattiva animazione 

La sincronizzazione di Espresso, che non sa come aspettare il completamento dell'animazione, può causare il fallimento di alcuni test, se si consente l'animazione sul dispositivo di test. Per disattivare l'animazione sul dispositivo di prova, vai a impostazioni > Opzioni per gli sviluppatori e disattiva tutte le seguenti opzioni nella sezione "Disegno": 

  • Scala di animazione della finestra
  • Scala di animazione di transizione
  • Scala della durata dell'animatore

5. Scrivi il tuo primo test in Espresso

Innanzitutto, iniziamo a testare una schermata di accesso. Ecco come inizia il flusso di accesso: l'utente avvia l'app e la prima schermata visualizzata contiene un singolo Accesso pulsante. Quando quello Accesso si fa clic sul pulsante, si apre il LoginActivity schermo. Questa schermata contiene solo due Modifica il testos (i campi nome utente e password) e a Sottoscrivi pulsante. 

Ecco cosa è il nostro Attività principale il layout è simile a:

Ecco cosa è il nostro LoginActivity il layout è simile a:

Ora scriviamo un test per il nostro Attività principale classe. Vai al tuo Attività principale classe, sposta il cursore su Attività principale nome e premere Maiuscole-Ctrl-T. Selezionare Crea nuovo test ... nel menu popup. 

premi il ok pulsante e viene visualizzata un'altra finestra di dialogo. Scegli il androidTest directory e fare clic su ok pulsante ancora una volta. Si noti che poiché stiamo scrivendo un test di strumentazione (test specifici dell'SDK di Android), i test case risiedono nel androidTest / java cartella. 

Ora, Android Studio ha creato con successo una classe di test per noi. Sopra il nome della classe, includi questa annotazione: @RunWith (AndroidJUnit4 :: classe).

import android.support.test.runner.AndroidJUnit4 import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) class MainActivityTest 

Questa annotazione indica che tutti i test in questa classe sono test specifici per Android.

Attività di test

Perché vogliamo testare un'attività, dobbiamo fare una piccola installazione. Dobbiamo informare Espresso su quale Attività aprire o lanciare prima dell'esecuzione e distruzione dopo l'esecuzione di qualsiasi metodo di prova. 

import android.support.test.rule.ActivityTestRule import android.support.test.runner.AndroidJUnit4 import org.junit.Rule import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) class MainActivityTest @Rule @JvmField var activityRule = ActivityTestRule(MainActivity :: class.java)

Si noti che il @Regola annotazione significa che questa è una regola di test JUnit4. Le regole del test JUnit4 vengono eseguite prima e dopo ogni metodo di prova (annotato con @Test). Nel nostro scenario, vogliamo lanciare Attività principale prima di ogni metodo di prova e distruggerlo dopo. 

Abbiamo anche incluso il @JvmField Annotazione Kotlin Questo semplicemente indica al compilatore di non generare getter e setter per la proprietà e invece di esporlo come un semplice campo Java.

Ecco i tre passaggi principali per scrivere un test Espresso:

  • Cerca il widget (ad es. TextView o Pulsante) vuoi testare.
  • Esegui una o più azioni su quel widget. 
  • Verifica o verifica se quel widget si trova ora in un determinato stato. 

I seguenti tipi di annotazioni possono essere applicati ai metodi utilizzati all'interno della classe di test.

  • @Prima della lezione: indica che il metodo statico a cui questa annotazione è applicata deve essere eseguito una volta e prima di tutti i test della classe. Questo potrebbe essere utilizzato, ad esempio, per impostare una connessione a un database. 
  • @Prima: indica che il metodo a cui questa annotazione è collegata deve essere eseguito prima di ogni metodo di test nella classe.
  • @Test: indica che il metodo a cui questa annotazione è collegata deve essere eseguito come un caso di test.
  • @Dopo: indica che il metodo a cui questa annotazione è collegata deve essere eseguito dopo ogni metodo di prova. 
  • @Dopo la lezione: indica che il metodo a cui questa annotazione è collegata deve essere eseguito dopo aver eseguito tutti i metodi di test nella classe. Qui, in genere, chiudiamo le risorse che sono state aperte @Prima della lezione

Trova un vista utilizzando OnView ()

Nel nostro Attività principale file di layout, abbiamo solo un widget-the Accesso pulsante. Proviamo uno scenario in cui un utente troverà quel pulsante e farà clic su di esso.

importare android.support.test.espresso.Espresso.onView import android.support.test.espresso.matcher.ViewMatchers.withId // ... @RunWith (AndroidJUnit4 :: class) class MainActivityTest // ... @Test @Throws (Eccezione: : class) fun clickLoginButton_opensLoginUi () onView (withId (R.id.btn_login))

Per trovare i widget in Espresso, utilizziamo il OnView () metodo statico (invece di findViewById ()). Il tipo di parametro che forniamo OnView () è un Matcher. Si noti che il Matcher L'API non proviene dall'SDK di Android ma dal Progetto Hamcrest. La libreria di abbinamenti di Hamcrest è all'interno della libreria dell'Espresso che abbiamo tirato via Gradle. 

Il OnView (withId (R.id.btn_login)) restituirà a ViewInteraction questo è per a vista il cui ID è R.id.btn_login. Nell'esempio sopra, abbiamo usato withId () cercare un widget con un determinato ID. Altri abbinatori di vista che possiamo usare sono: 

  • withText (): restituisce un match che corrisponde TextView in base al valore della proprietà del testo.
  • withHint (): restituisce un match che corrisponde TextView in base al valore della proprietà suggerimento.
  • withTagKey (): restituisce un match che corrisponde vista basato su chiavi di tag.
  • withTagValue (): restituisce un match che corrisponde vistas basato sui valori delle proprietà del tag.

Per prima cosa, proviamo a vedere se il pulsante è effettivamente visualizzato sullo schermo. 

OnView (withId (R.id.btn_login)). controllare (partite (isDisplayed ()))

Qui, stiamo solo confermando se il pulsante con l'ID specificato (R.id.btn_login) è visibile all'utente, quindi usiamo il dai un'occhiata() metodo per confermare se il sottostante vista ha un certo stato, nel nostro caso, se è visibile.

Il partite () il metodo statico restituisce un generico ViewAssertion che asserisce che una vista esiste nella gerarchia della vista ed è abbinata dal dato corrispondente. Quel matcher vista data viene restituito chiamando È visualizzato(). Come suggerito dal nome del metodo, È visualizzato() è un match che corrisponde vistas che sono attualmente visualizzati sullo schermo all'utente. Ad esempio, se vogliamo verificare se un pulsante è abilitato, passiamo semplicemente è abilitato() a partite ()

Altri fiammiferi di vista popolari che possiamo passare nel partite () metodo sono:

  • hasFocus (): restituisce un match che corrisponde vistas che al momento hanno focus.
  • IsChecked (): restituisce un matcher che accetta se e solo se la vista è a CompoundButton (o sottotipo di) ed è in stato controllato. L'opposto di questo metodo è isNotChecked ()
  • è selezionato(): restituisce un match che corrisponde vistas che sono selezionati.

Per eseguire il test, puoi fare clic sul triangolo verde accanto al metodo o sul nome della classe. Facendo clic sul triangolo verde accanto al nome della classe verranno eseguiti tutti i metodi di test in quella classe, mentre quello accanto a un metodo eseguirà il test solo per quel metodo. 

Evviva! Il nostro test è passato!


Eseguire azioni su una vista

A ViewInteraction oggetto che viene restituito chiamando OnView (), possiamo simulare azioni che un utente può eseguire su un widget. Ad esempio, possiamo simulare un'azione di clic semplicemente chiamando il clic() metodo statico all'interno del ViewActions classe. Questo restituirà a ViewAction oggetto per noi. 

La documentazione dice questo ViewAction è:

Responsabile per l'esecuzione di un'interazione sull'elemento Vista specificato.
@Test fun clickLoginButton_opensLoginUi () // ... onView (withId (R.id.btn_login)). Perform (clicca ())

Eseguiamo un evento click prima chiamando eseguire(). Questo metodo esegue le azioni specificate sulla vista selezionata dal correttore visualizzazione corrente. Nota che possiamo passargli una singola azione o una lista di azioni (eseguite in ordine). Qui, l'abbiamo dato clic(). Altre possibili azioni sono:

  • TypeText () imitare il testo digitando in un Modifica il testo.
  • testo chiaro() per simulare il testo di cancellazione in un Modifica il testo.
  • doppio click() per simulare il doppio clic su vista.
  • longClick () imitare il clic lungo a vista.
  • scrollTo () per simulare lo scorrimento a ScrollView ad un particolare vista è visibile. 
  • scorrere verso sinistra() per simulare lo scorrimento da destra a sinistra attraverso il centro verticale di a vista.

Molte altre simulazioni possono essere trovate all'interno del ViewActions classe. 

Convalida con le asserzioni di visualizzazione

Completiamo il nostro test, per verificare che il LoginActivity lo schermo è mostrato ogni volta che il Accesso il pulsante viene cliccato. Anche se abbiamo già visto come si usa dai un'occhiata() a ViewInteraction, usiamolo di nuovo, passandogli un altro ViewAssertion

@Test fun clickLoginButton_opensLoginUi () // ... onView (withId (R.id.tv_login)). Check (matches (isDisplayed ()))

Dentro il LoginActivity file di layout, a parte Modifica il testos e a Pulsante, abbiamo anche un TextView con ID R.id.tv_login. Quindi facciamo semplicemente un controllo per confermare che il TextView è visibile all'utente. 

Ora puoi eseguire nuovamente il test!

I test dovrebbero passare correttamente se hai seguito correttamente tutti i passaggi. 

Ecco cosa è successo durante il processo di esecuzione dei nostri test: 

  1. Lanciato il Attività principale usando il activityRule campo.
  2. Verificato se il Accesso pulsante (R.id.btn_login) era visibile (È visualizzato()) per l'utente.
  3. Simulazione di un'azione di clic (clic()) su quel pulsante.
  4. Verificato se il LoginActivity è stato mostrato all'utente verificando se a TextView con id R.id.tv_login nel LoginActivity è visibile.

Puoi sempre fare riferimento al foglio dei trucchi Espresso per vedere i diversi abbinamenti di visualizzazioni, visualizzare le azioni e visualizzare le asserzioni disponibili. 

6. Testare il LoginActivity Schermo

Ecco il nostro LoginActivity.kt:

import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Button import android.widget.EditText import android.widget.TextView class LoginActivity: AppCompatActivity () private lateinit var usernameEditText: EditText private lateinit var loginTitleTextView: TextView private lateinit var passwordEditText: EditText private lateinit var submitButton: Button override fun onCreate (savedInstanceState: Bundle?) super.onCreate (savedInstanceState) setContentView (R.layout.activity_login) usernameEditText = findViewById (R.id.et_username) passwordEditText = findViewById (R.id.et_password) submitButton = findViewById (R.id.btn_submit) loginTitleTextView = findViewById (R.id.tv_login) submitButton.setOnClickListener if (usernameEditText.text.toString () == "chike" && passwordEditText. text.toString () == "password") loginTitleTextView.text = "Success" else loginTitleTextView.text = "Fallimento"

Nel codice sopra, se il nome utente inserito è "chike" e la password è "password", l'accesso è riuscito. Per qualsiasi altro input, è un fallimento. Scriviamo ora un test Espresso per questo!

Vai a LoginActivity.kt, sposta il cursore su LoginActivity nome e premere Maiuscole-Ctrl-T. Selezionare Crea nuovo test ...  nel menu popup. Segui lo stesso processo che abbiamo fatto per MainActivity.kt, e fare clic su ok pulsante. 

importare android.support.test.espresso.Espresso import android.support.test.espresso.Espresso.onView importare android.support.test.espresso.action.ViewActions importare android.support.test.espresso.assertion.ViewAssertions.matches importare android .support.test.espresso.matcher.ViewMatchers.withId import android.support.test.espresso.matcher.ViewMatchers.withText import android.support.test.rule.ActivityTestRule import android.support.test.runner.AndroidJUnit4 import org.junit .Rule import org.junit.Test import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) class LoginActivityTest @Rule @JvmField var activityRule = ActivityTestRule(LoginActivity :: class.java) private val username = "chike" private val password = "password" @Test fun clickLoginButton_opensLoginUi () onView (withId (R.id.et_username)). Perform (ViewActions.typeText (username)) onView (withId (R.id.et_password)). perform (ViewActions.typeText (password)) onView (withId (R.id.btn_submit)). perform (ViewActions.scrollTo (), ViewActions.click ()) Espresso.onView (withId (R.id.tv_login)) .check (matches (withText ("Success")))

Questa classe di test è molto simile alla nostra prima. Se eseguiamo il test, il nostro LoginActivity lo schermo è aperto. Il nome utente e la password sono digitati nel R.id.et_username e R.id.et_password campi rispettivamente. Successivamente, l'espresso farà clic sul Sottoscrivi pulsante (R.id.btn_submit). Aspetterà fino a a vista con id R.id.tv_login può essere trovato con la lettura del testo Successo

7. Test a RecyclerView

RecyclerViewActions è la classe che espone un set di API per operare su a RecyclerView. RecyclerViewActions fa parte di un artefatto separato all'interno del espresso-contrib artefatto, che dovrebbe anche essere aggiunto a build.gradle:

androidTestImplementation 'com.android.support.test.espresso: espresso-contrib: 3.0.2' 

Nota che questo artefatto contiene anche l'API per l'interfaccia utente che verifica il cassetto di navigazione DrawerActions e DrawerMatchers

@RunWith (AndroidJUnit4 :: class) class MyListActivityTest // ... @Test fun clickItem () onView (withId (R.id.rv)) .perform (RecyclerViewActions .actionOnItemAtPosition(0, ViewActions.click ()))

Per fare clic su un elemento in qualsiasi posizione in a RecyclerView, noi invochiamo actionOnItemAtPosition (). Dobbiamo dargli un tipo di oggetto. Nel nostro caso, l'oggetto è il ViewHolder classe dentro la nostra RandomAdapter. Questo metodo accetta anche due parametri; il primo è la posizione, e il secondo è l'azione (ViewActions.click ()). 

Altro RecyclerViewActions che possono essere eseguiti sono:

  • actionOnHolderItem (): esegue a ViewAction su una vista abbinata a viewHolderMatcher. Questo ci permette di abbinarlo a ciò che è contenuto nel ViewHolder piuttosto che la posizione. 
  • ScrollToPosition (): restituisce a ViewAction che scorre RecyclerView in una posizione.

Successivamente (una volta aperta la schermata "aggiungi nota"), inseriremo il testo della nota e salveremo la nota. Non è necessario attendere l'apertura della nuova schermata: Espresso lo farà automaticamente per noi. Aspetta fino a una vista con l'id R.id.add_note_title può essere trovato.

8. Test Intents

L'espresso utilizza un altro artefatto chiamato espresso-intenti per testare gli intent. Questo artefatto è solo un'altra estensione di Espresso che si concentra sulla convalida e la presa in giro degli Intenti. Diamo un'occhiata a un esempio.

In primo luogo, dobbiamo tirare il espresso-intenti biblioteca nel nostro progetto. 

androidTestImplementation 'com.android.support.test.espresso: espresso-intents: 3.0.2'
importare android.support.test.espresso.intent.rule.IntentsTestRule import android.support.test.runner.AndroidJUnit4 import org.junit.Rule import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) class PickContactActivityTest @ Regola @JvmField var intentRule = IntentsTestRule(PickContactActivity :: class.java)

IntentsTestRule si estende ActivityTestRule, quindi entrambi hanno comportamenti simili. Ecco cosa dice il doc:

Questa classe è un'estensione di ActivityTestRule, che inizializza Espresso-Intents prima di ogni prova annotata con Test e rilascia Espresso-Intents dopo ogni prova. L'attività verrà terminata dopo ogni test e questa regola può essere utilizzata allo stesso modo di ActivityTestRule.

La principale caratteristica di differenziazione è che ha funzionalità aggiuntive per il test startActivity () e startSubActivity () con mock e mozziconi. 

Stiamo testando uno scenario in cui un utente fa clic su un pulsante (R.id.btn_select_contact) sullo schermo per selezionare un contatto dall'elenco dei contatti del telefono. 

// ... @Test fun stubPick () var result = Instrumentation.ActivityResult (Activity.RESULT_OK, Intent (null, ContactsContract.Contacts.CONTENT_URI)) intending (hasAction (Intent.ACTION_PICK)). ReplyWith (result) onView (withId ( R.id.btn_select_contact)). Perform (click ()) inteso (allOf (toPackage ("com.google.android.contacts"), hasAction (Intent.ACTION_PICK), hasData (ContactsContract.Contacts.CONTENT_URI)))) // ...

Qui stiamo usando l'intenzione () dal espresso-intenti libreria per impostare uno stub con una risposta finta per il nostro ACTION_PICK richiesta. Ecco cosa succede in  PickContactActivity.kt quando l'utente fa clic sul pulsante con id R.id.btn_select_contact scegliere un contatto.

divertimento pickContact (v: View) val i = Intent (Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) startActivityForResult (i, PICK_REQUEST)

l'intenzione () accetta a Matcher che corrisponde agli intenti per i quali deve essere fornita una risposta di risposta. In altre parole, il Matcher identifica la richiesta che ti interessa allo stub. Nel nostro caso, ci avvaliamo di hasAction () (un metodo di supporto in IntentMatchers) per trovare il nostro ACTION_PICK richiesta. Quindi invochiamo respondWith (), che stabilisce il risultato onActivityResult (). Nel nostro caso, il risultato è Activity.RESULT_OK, simulare l'utente selezionando un contatto dall'elenco. 

Quindi simuliamo il clic sul pulsante Seleziona contatto, che chiama startSubActivity (). Si noti che il nostro stub ha inviato la risposta simulata a onActivityResult ()

Finalmente, usiamo il inteso () metodo di supporto per semplicemente convalidare le chiamate a startActivity () e startSubActivity () sono stati fatti con le giuste informazioni. 

Conclusione

In questo tutorial, hai imparato come utilizzare facilmente il framework di test Espresso nel tuo progetto Android Studio per automatizzare il tuo workflow di test. 

Consiglio vivamente di consultare la documentazione ufficiale per ulteriori informazioni sulla scrittura di test dell'interfaccia utente con Espresso.