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.
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:
usernameEditText
e passwordEditText
sono visibili. 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:
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
Per poter seguire questo tutorial, avrai bisogno di:
Un esempio di progetto (in Kotlin) per questo tutorial può essere trovato sul nostro repository GitHub in modo da poter seguire facilmente.
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.
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.
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":
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 testo
s (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.
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:
TextView
o Pulsante
) vuoi testare.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
. 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 vista
s 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 vista
s 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 vista
s 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 vista
s 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!
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.
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 testo
s 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:
Attività principale
usando il activityRule
campo.R.id.btn_login
) era visibile (È visualizzato()
) per l'utente.clic()
) su quel pulsante.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.
LoginActivity
SchermoEcco 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.
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.
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 diActivityTestRule
, che inizializza Espresso-Intents prima di ogni prova annotata conTest
e rilascia Espresso-Intents dopo ogni prova. L'attività verrà terminata dopo ogni test e questa regola può essere utilizzata allo stesso modo diActivityTestRule
.
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.
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.