Testarea interfețelor de utilizator Android cu Espresso

În acest post, veți afla despre cum să scrieți testele UI cu cadrul de testare Espresso și să vă automatizați fluxul de lucru pentru testare, în loc să utilizați procesul manuale dificil și cu predispoziție la erori. 

Espresso este un cadru de testare pentru scrierea testelor UI în Android. Conform documentelor oficiale, puteți:

Utilizați Espresso pentru a scrie teste concise, frumoase și fiabile cu UI Android.

1. De ce să utilizați espresso?

Una dintre problemele cu testarea manuală este că poate fi consumatoare de timp și dificil de efectuat. De exemplu, pentru a testa un ecran de conectare (manual) într-o aplicație Android, va trebui să faceți următoarele:

  1. Lansați aplicația. 
  2. Navigați la ecranul de conectare. 
  3. Confirmați dacă usernameEditText și passwordEditText sunt vizibile. 
  4. Introduceți numele de utilizator și parola în câmpurile respective. 
  5. Confirmați dacă butonul de conectare este de asemenea vizibil, apoi faceți clic pe acel buton de conectare.
  6. Verificați dacă afișările corecte sunt afișate atunci când acel login a avut succes sau a fost un eșec. 

În loc să petrecem tot timpul acest test manual, ar fi mai bine să petrecem mai mult timp scriind codul care face ca aplicația să iasă în evidență de restul! Și, chiar dacă testarea manuală este obositoare și destul de lentă, este încă predispusă la erori și este posibil să pierdeți unele cazuri de colț. 

Unele dintre avantajele testării automate includ următoarele:   

  • Testele automate execută exact aceleași cazuri de testare de fiecare dată când sunt executate. 
  • Dezvoltatorii pot detecta rapid o problemă rapid înainte de a fi trimisi echipei de asigurare a calității. 
  • Se poate economisi o mulțime de timp, spre deosebire de efectuarea testelor manuale. Economisind timp, inginerii software și echipa QA pot petrece mai mult timp pe sarcini dificile și pline de satisfacții. 
  • Se obține o acoperire mai mare a testelor, ceea ce duce la o aplicare mai bună a calității. 

În acest tutorial, vom afla despre Espresso integrand-o într-un proiect Android Studio. Vom scrie teste UI pentru un ecran de conectare și o RecyclerView, și vom afla despre testarea intențiilor. 

Calitatea nu este un act, este un obicei. - Pablo Picasso

2. Cerințe preliminare

Pentru a putea urma acest tutorial, veți avea nevoie de:

  • o înțelegere de bază a API-urilor Android core și Kotlin
  • Android Studio 3.1.3 sau o versiune ulterioară
  • Kotlin plugin 1.2.51 sau mai mare

Un exemplu de proiect (în Kotlin) pentru acest tutorial poate fi găsit pe replica noastră GitHub, pentru a putea urmări cu ușurință.

3. Creați un proiect Android Studio

Activați Android Studio 3 și creați un nou proiect cu o activitate goală numită Activitate principala. Asigurați-vă că ați verificat Includeți suportul Kotlin

4. Set Up Espresso și AndroidJUnitRunner

După ce ați creat un nou proiect, asigurați-vă că adăugați următoarele dependențe din Biblioteca de suport pentru testare Android din aplicația dvs. build.gradle (deși Android Studio le-a inclus deja pentru noi). În acest tutorial, folosim cea mai recentă versiune de bibliotecă Espresso 3.0.2 (din această scriere). 

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

Am inclus și alergătorul de instrumente AndroidJUnitRunner:

Un Instrumentaţie care execută testele JUnit3 și JUnit4 împotriva unui pachet Android (aplicație).

Rețineți că Instrumentaţie este pur și simplu o clasă de bază pentru implementarea codului de instrumentație aplicație. 

Dezactivați animația 

Sincronizarea aplicației Espresso, care nu știe să aștepte terminarea unei animații, poate duce la eșecul unor teste - dacă permiteți animația pe dispozitivul de testare. Pentru a dezactiva animația pe dispozitivul dvs. de testare, accesați Setări > Opțiuni pentru dezvoltatori și dezactivați toate opțiunile de mai jos în secțiunea "Desenare": 

  • Gama de animație pe ferestre
  • Scală de animație de tranziție
  • Scara pentru durata animatorului

5. Scrieți primul test în espresso

În primul rând, începem să testați un ecran de conectare. Iată cum pornește fluxul de conectare: utilizatorul lansează aplicația, iar primul ecran afișat conține un singur Logare buton. Cand asta Logare butonul este apăsat, se deschide LoginActivity ecran. Acest ecran conține doar două Editează textuls (câmpurile de nume de utilizator și parola) și a A depune buton. 

Iată ce e al nostru Activitate principala aspectul arata ca:

Iată ce e al nostru LoginActivity aspectul arata ca:

Să scriem acum un test pentru noi Activitate principala clasă. Du-te la tine Activitate principala clasa, mutați cursorul pe Activitate principala numele și apăsați Shift-Control-T. Selectați Creați un test nou ... în meniul pop-up. 

apasă pe O.K butonul și se va afișa un alt dialog. Alege androidTest și faceți clic pe O.K butonul încă o dată. Rețineți că, pentru că scriem un test de instrumentație (teste specifice SDK pentru Android), cazurile de testare se află în androidTest / java pliant. 

Acum, Android Studio a creat cu succes o clasă de testare pentru noi. Deasupra numelui clasei, includeți această adnotare: @RunWith (AndroidJUnit4 :: clasă).

importați și clasa MainActivityTest clasa de import android.support.test.runner.AndroidJUnit4 org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class)

Această adnotare înseamnă că toate testele din această clasă sunt teste specifice pentru Android.

Activități de testare

Pentru că vrem să testăm o Activitate, trebuie să facem o mică pregătire. Trebuie să informăm Espresso care Activitate de deschidere sau de lansare înainte de executarea și distrugerea după executarea oricărei metode de testare. 

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

Rețineți că @Regulă adnotarea înseamnă că aceasta este o regulă de test JUnit4. Regulile de testare JUnit4 sunt executate înainte și după fiecare metodă de testare (adnotată cu @Test). În scenariul nostru, dorim să lansăm Activitate principala înainte de fiecare metodă de testare și distrugeți-o după. 

Am inclus și @JvmField Adnotarea Kotlin. Aceasta instruiește pur și simplu compilatorul să nu genereze getters și setteri pentru proprietate și în loc să-l expună ca un simplu câmp Java.

Iată trei etape majore în scrierea unui test Espresso:

  • Căutați miniaplicația (de ex. TextView sau Buton) pe care doriți să le testați.
  • Efectuați una sau mai multe acțiuni asupra acelui widget. 
  • Verificați sau verificați dacă acest widget este acum într-o anumită stare. 

Următoarele tipuri de adnotări pot fi aplicate metodelor utilizate în cadrul clasei de test.

  • @Înainte de curs: aceasta indică faptul că metoda statică la care se aplică această adnotare trebuie să fie executată o dată și înainte de toate testele din clasă. Aceasta ar putea fi folosită, de exemplu, pentru a configura o conexiune la o bază de date. 
  • @Inainte de: indică faptul că metoda la care se atașează această adnotare trebuie să fie executată înainte de fiecare metodă de testare din clasă.
  • @Test: indică faptul că metoda la care se atașează această adnotare ar trebui să ruleze ca un caz de test.
  • @După: indică faptul că metoda la care se atașează această adnotare ar trebui să ruleze după fiecare metodă de testare. 
  • @După clasa: indică faptul că metoda la care se atașează această adnotare ar trebui să ruleze după ce au fost executate toate metodele de testare din clasă. Aici, de obicei, închidem resursele care au fost deschise @Înainte de curs

Gaseste un Vedere Utilizarea onView ()

În a noastră Activitate principala layout file, avem doar un widget - Logare buton. Să încercăm un scenariu în care un utilizator să găsească butonul și să facă clic pe el.

importand android.support.test.espresso.Espresso.onView importand clasa MainActivityTest si /roid.support.test.espresso.matcher.ViewMatchers.withId // ... @RunWith (AndroidJUnit4 :: class) // // ... @Test @Throws (Exceptie: : class) distracție clickLoginButton_opensLoginUi () onView (cuId (R.id.btn_login))

Pentru a găsi widget-uri în Espresso, facem uz de onView () metoda statică (în loc de findViewById ()). Tipul de parametru pe care îl furnizăm onView () este a Matcher. Rețineți că Matcher API nu vine din SDK Android, ci din Proiectul Hamcrest. Biblioteca lui Hamcrest este cuprinsă în biblioteca Espresso pe care am tras-o prin Gradle. 

onView (withId (R.id.btn_login)) va returna a ViewInteraction că este pentru un Vedere a cărui identitate este R.id.btn_login. În exemplul de mai sus, am folosit withId () să căutați un widget cu un id dat. Alte vizualizări pe care le putem folosi sunt: 

  • withText (): returnează o potrivire potrivită TextView pe baza valorii sale de proprietate text.
  • withHint (): returnează o potrivire potrivită TextView bazat pe valoarea proprietății indiciu.
  • withTagKey (): returnează o potrivire potrivită Vedere pe baza cheilor de etichetare.
  • withTagValue (): returnează o potrivire potrivită Vederes pe baza valorilor proprietății tag-urilor.

Mai întâi, hai să verificăm dacă butonul este afișat de fapt pe ecran. 

onView (withId (R.id.btn_login)). verifică (meciuri (isDisplayed ()))

Aici, confirmăm doar dacă butonul cu ID-ul dat (R.id.btn_login) este vizibil pentru utilizator, deci folosim Verifica() pentru a confirma dacă subiacentă Vedere are o anumită stare - în cazul nostru, dacă este vizibilă.

chibrituri() metoda statică returnează o generică ViewAssertion care afirmă că există o viziune în ierarhia vizuală și că este potrivită de matricea vizuală dată. Acea vizualizare a matcherului este returnată prin apelare este afisat(). După cum este sugerat de numele metodei, este afisat() este o potrivire potrivită Vederes care sunt afișate în prezent pe ecran către utilizator. De exemplu, dacă vrem să verificăm dacă este activat un buton, pur și simplu treceți este activat() la chibrituri()

Alte materiale de vizualizare populare pe care le putem trece în chibrituri() sunt:

  • hasFocus (): returnează o potrivire potrivită Vederecare se concentrează în prezent.
  • este bifat(): returnează o matrice care acceptă dacă și numai dacă vizualizarea este a CompoundButton (sau subtipul) și este în stare verificată. Opusul acestei metode este isNotChecked ()
  • este selectat(): returnează o potrivire potrivită Vederes care sunt selectate.

Pentru a executa testul, puteți face clic pe triunghiul verde de lângă metoda sau numele clasei. Dacă faceți clic pe triunghiul verde de lângă numele clasei, veți rula toate metodele de testare din clasa respectivă, în timp ce cel de lângă o metodă va executa testul numai pentru acea metodă. 

Ura! Testul nostru a trecut!


Efectuați acțiuni pe o vizualizare

Pe o ViewInteraction obiect care este returnat prin apelare onView (), putem simula acțiunile pe care un utilizator le poate efectua pe un widget. De exemplu, putem simula o acțiune de clic prin simpla apelare a acesteia clic() metoda statică în interiorul ViewActions clasă. Aceasta va reveni a ViewAction obiect pentru noi. 

Documentația spune asta ViewAction este:

Responsabil pentru realizarea unei interacțiuni pe elementul Vizualizare dat.
@Test distractiv clickLoginButton_opensLoginUi () // ... onView (cuId (R.id.btn_login)) efectuați (faceți clic pe ())

Realizăm un eveniment de clic efectuând o primă apelare a executa(). Această metodă efectuează acțiunea (acțiunile) dată în vizualizarea selectată de vizualizatorul curent. Rețineți că putem transmite o singură acțiune sau o listă de acțiuni (executate în ordine). Aici am dat-o clic(). Alte acțiuni posibile sunt:

  • TYPETEXT () pentru a imita textul într-un text Editează textul.
  • text clar() pentru a simula textul de compensare într - un Editează textul.
  • dublu click() pentru a simula dublu-clic pe Vedere.
  • longClick () pentru a imita cu un clic lung Vedere.
  • scrollTo () pentru a simula defilarea a ScrollView la un anumit Vedere care este vizibil. 
  • swipeLeft () pentru a simula rotire spre dreapta spre stânga peste centrul vertical al unui Vedere.

Mai multe simulări pot fi găsite în interiorul ViewActions clasă. 

Validați cu afirmații de vedere

Să terminăm testul, pentru a valida acest lucru LoginActivity ecranul este afișat ori de câte ori Logare butonul este apăsat. Deși am văzut deja cum să folosim Verifica() pe o ViewInteraction, să-l folosim din nou, să-l trecem pe altul ViewAssertion

@Test distractiv clickLoginButton_opensLoginUi () // ... onView (cuId (R.id.tv_login)) check (se potrivește (isDisplayed ()))

În interiorul LoginActivity layout file, în afară de Editează textuls și a Buton, avem de asemenea un TextView cu ID R.id.tv_login. Deci, pur și simplu facem un cec pentru a confirma că TextView este vizibil pentru utilizator. 

Acum puteți trece din nou la test!

Testele dvs. ar trebui să treacă cu succes dacă ați urmat corect toate etapele. 

Iată ce sa întâmplat în timpul procesului de efectuare a testelor noastre: 

  1. Lansat Activitate principala folosind activityRule camp.
  2. Verificat dacă Logare butonul (R.id.btn_login) a fost vizibilă (este afisat()) către utilizator.
  3. Simularea unei acțiuni de clic (clic()) pe acel buton.
  4. Verificat dacă LoginActivity a fost arătată utilizatorului prin verificarea dacă a TextView cu id R.id.tv_login în LoginActivity este vizibil.

Puteți să consultați întotdeauna foaia de înșelătorie Espresso pentru a vedea diferitele materiale de vizualizare, acțiunile de vizualizare și afirmațiile de afirmații disponibile. 

6. Testați LoginActivity Ecran

Iată-ne pe noi LoginActivity.kt:

import android.os.Bundle import șiroid.support.v7.app.AppCompatActivity import android.widget.Button de import android.widget.EditText de import android.widget.TextView clasa LoginActivity: AppCompatActivity () privat lateinit var usernameEditText: EditText privat lateinit var loginTitleTextView: TextView privat lateinit var parolaEditText: EditText privat lateinit var submitButton: buton 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 dacă (usernameEditText.text.toString () == "chike" && passwordEditText. text.toString () == "parola") loginTitleTextView.text = "Succes" altceva loginTitleTextView.text = "Failure"

În codul de mai sus, dacă numele de utilizator introdus este "chike" și parola este "parola", atunci login-ul are succes. Pentru orice altă intrare, e un eșec. Să scriem acum un test Espresso pentru acest lucru!

Mergi la LoginActivity.kt, mutați cursorul pe LoginActivity numele și apăsați Shift-Control-T. Selectați Creați un test nou ...  în meniul pop-up. Urmați același proces ca și pentru noi MainActivity.kt, și faceți clic pe O.K buton. 

importați android.support.test.espresso.Espresso importați android.support.test.espresso.Espresso.onVizualizați importul android.support.test.espresso.action.ViewActions importați android.support.test.espresso.assertion.ViewAssertions.matches importați 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.run.runner.AndroidJUnit4 import org.junit .Rule de import org.junit.Test import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) clasa LoginActivityTest @Rule @JvmField var activityRule = ActivityTestRule(LoginActivity :: class.java) private val username = "chike" private val password = "password" @Test distractiv clickLoginButton_opensLoginUi () onView (cuId (R.id.et_username)) perform (ViewActions.typeText (username)) onView (cu Id (R.id.et_password)) executa (ViewActions.typeText (parola)) onView (withId (R.id.btn_submit)) executa (ViewActions.scrollTo (), ViewActions.click ()) Espresso.onView (cu Id (R.id.tv_login)) .check (se potrivește (cu Text ("Succes")))

Această clasă de testare este foarte asemănătoare cu prima noastră. Dacă conducem testul, a noastră LoginActivity ecranul este deschis. Numele de utilizator și parola sunt introduse în R.id.et_username și R.id.et_password câmpuri respective. Apoi, Espresso va face clic pe A depune butonul (R.id.btn_submit). Va astepta pana la Vedere cu id R.id.tv_login pot fi găsite cu citirea textului Succes

7. Testați a RecyclerView

RecyclerViewActions este clasa care expune un set de API-uri care să opereze pe RecyclerView. RecyclerViewActions face parte dintr-un artefact separat din interiorul espresso-contrib artefact, care ar trebui, de asemenea, să fie adăugat la build.gradle:

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

Rețineți că acest artefact conține, de asemenea, API-ul pentru UI care testează sertarul de navigație DrawerActions și DrawerMatchers

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

Pentru a face clic pe un element în orice poziție din a RecyclerView, noi invocăm actionOnItemAtPosition (). Trebuie să-i dăm un tip de element. În cazul nostru, elementul este ViewHolder clasă în interiorul nostru RandomAdapter. Această metodă include și doi parametri; prima este poziția, iar a doua este acțiunea (ViewActions.click ()). 

Alte RecyclerViewActions care pot fi efectuate sunt:

  • actionOnHolderItem (): efectuează a ViewAction pe o vizualizare potrivită de viewHolderMatcher. Acest lucru ne permite să ne potrivim cu ceea ce este conținut în interiorul ViewHolder mai degrabă decât poziția. 
  • scrollToPosition (): returnează a ViewAction care derulează RecyclerView într-o poziție.

Înainte (odată ce ecranul "adăugați nota" este deschis), vom introduce textul notei noastre și vom salva nota. Nu trebuie să așteptăm deschiderea noului ecran - Espresso va face acest lucru automat pentru noi. Se așteaptă până la o vizualizare cu id-ul R.id.add_note_title poate fi găsit.

8. Încercați intențiile

Espresso folosește un alt artifact numit espresso-intențiile pentru testarea intențiilor. Acest artefact este doar o altă extensie la Espresso care se axează pe validarea și batjocurarea intențiilor. Să ne uităm la un exemplu.

În primul rând, trebuie să tragem espresso-intențiile biblioteca în proiectul nostru. 

androidTestImplementation 'com.android.support.test.espresso: espresso-intentions: 3.0.2'
importați android.support.test.espresso.intent.rule.IntentsTestRule importați șiroid.support.test.run.runner.AndroidJUnit4 import org.junit.Rule import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) clasa PickContactActivityTest @ Regula @JvmField var intentRule = IntentsTestRule(PickContactActivity :: class.java)

IntentsTestRule extinde ActivityTestRule, astfel încât ambii au comportamente similare. Iată ce spune docul:

Această clasă este o extensie a ActivityTestRule, care inițializează intențiile espresso înainte de fiecare încercare adnotată cu Test și eliberează intențiile Espresso după fiecare test. Activitatea va fi terminată după fiecare test și această regulă poate fi utilizată în același mod ca și ActivityTestRule.

Principala caracteristică de diferențiere este că dispune de funcționalități suplimentare pentru testare startActivity () și startActivityForResult () cu batjocuri și bătăi de cap. 

Vom încerca acum un scenariu în care un utilizator va face clic pe un buton (R.id.btn_select_contact) de pe ecran pentru a alege un contact din lista de contacte a telefonului. 

// ... @ Test de distracție stubPick () var rezultat = Instrumentation.ActivityResult (Activity.RESULT_OK, Intent (null, ContactsContract.Contacts.CONTENT_URI)) intenționează (hasAction (Intent.ACTION_PICK) R.id.btn_select_contact)) efectuați (clic ()) intenționat (allOf (toPackage ("com.google.android.contacts"), hasAction (Intent.ACTION_PICK), hasData (ContactsContract.Contacts.CONTENT_URI) ...

Aici folosim intenționând () de la espresso-intențiile biblioteca pentru a crea un stub cu un raspuns fals pentru noi ACTION_PICK cerere. Iată ce se întâmplă  PickContactActivity.kt când utilizatorul face clic pe butonul cu id R.id.btn_select_contact pentru a alege un contact.

Distracție pickContact (v: Vizualizare) val i = Intent (Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) startActivityForResult (i, PICK_REQUEST)

intenționând () ia un a Matcher care se potrivește cu intențiile pentru care ar trebui furnizat un răspuns stubat. Cu alte cuvinte, Matcher identifică ce solicitare doriți să faceți. În cazul nostru, ne folosim hasAction () (metoda de ajutor în IntentMatchers) pentru a ne găsi ACTION_PICK cerere. Apoi, invocăm Raspunsulcu (), care stabilește rezultatul pentru onActivityResult (). În cazul nostru, rezultatul a fost Activity.RESULT_OK, simulând utilizatorul selectând un contact din listă. 

Apoi, simulam click pe butonul de selectare a contactelor, care sună startActivityForResult (). Rețineți că grupul nostru a trimis răspunsul fals onActivityResult ()

În cele din urmă, folosim destinate () metoda de ajutor pentru a valida pur și simplu că apelurile către startActivity () și startActivityForResult () au fost făcute cu informațiile corecte. 

Concluzie

În acest tutorial, ați învățat cum să utilizați cu ușurință cadrul de testare Espresso în proiectul dvs. Android Studio pentru a vă automatiza fluxul de lucru pentru testare. 

Vă recomandăm să verificați documentația oficială pentru a afla mai multe despre scrierea testelor UI cu Espresso. 

Cod