Testing Android Brukergrensesnitt Med Espresso

I dette innlegget lærer du om hvordan du skriver UI-tester med Espresso-testrammen og automatiserer test-arbeidsflyten din, i stedet for å bruke den kjedelige og svært feilberegnede manuelle prosessen. 

Espresso er et testramme for å skrive UI-tester i Android. I følge de offisielle dokumentene kan du:

Bruk Espresso til å skrive konsise, vakre og pålitelige Android UI-tester.

1. Hvorfor bruke Espresso?

Et av problemene med manuell testing er at det kan være tidkrevende og kjedelig å utføre. For eksempel, for å teste en påloggingsskjerm (manuelt) i en Android-app, må du gjøre følgende:

  1. Start appen. 
  2. Naviger til påloggingsskjermen. 
  3. Bekreft om usernameEditText og passwordEditText er synlige. 
  4. Skriv inn brukernavnet og passordet i de respektive feltene. 
  5. Bekreft om innloggingsknappen også er synlig, og klikk deretter på den innloggingsknappen.
  6. Sjekk om de riktige visningene vises når den påloggingen var vellykket eller var feil. 

I stedet for å bruke hele denne tiden på å teste vår app manuelt, ville det være bedre å bruke mer tid på å skrive kode som gjør at appen vår skiller seg ut fra resten! Og selv om manuell testing er kjedelig og ganske treg, er den fortsatt feilaktig og du kan savne noen hjørnesaker. 

Noen av fordelene med automatisert testing inkluderer følgende:   

  • Automatiserte tester utfører nøyaktig de samme test sakene hver gang de utføres. 
  • Utviklere kan raskt oppdage et problem raskt før det sendes til QA-teamet. 
  • Det kan spare mye tid, i motsetning til å gjøre manuell testing. Ved å spare tid kan programvareingeniører og QA-teamet i stedet bruke mer tid på utfordrende og givende oppgaver. 
  • Høyere testdekning oppnås, noe som fører til en bedre kvalitet. 

I denne opplæringen lærer vi om Espresso ved å integrere den i et Android Studio-prosjekt. Vi skriver UI-tester for en påloggingsskjerm og a RecyclerView, og vi vil lære om testing hensikter. 

Kvalitet er ikke en handling, det er en vane. - Pablo picasso

2. Forutsetninger

For å kunne følge denne opplæringen må du:

  • en grunnleggende forståelse av kjernen Android APIs og Kotlin
  • Android Studio 3.1.3 eller høyere
  • Kotlin plugin 1.2.51 eller høyere

Et prøveprosjekt (i Kotlin) for denne opplæringen finner du på vår GitHub repo, slik at du enkelt kan følge med.

3. Lag et Android Studio-prosjekt

Brann opp Android Studio 3 og opprett et nytt prosjekt med en tom aktivitet som heter Hoved aktivitet. Pass på å sjekke Inkluder Kotlin-støtte

4. Sett opp Espresso og AndroidJUnitRunner

Når du har opprettet et nytt prosjekt, må du sørge for å legge til følgende avhengigheter fra Android Testing Support Library i din build.gradle (selv om Android Studio allerede har tatt med dem for oss). I denne opplæringen bruker vi den nyeste Espresso-biblioteket versjon 3.0.2 (som av denne skrivingen). 

android // ... defaultConfig // ... testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // ... avhengigheter // ... androidTestImplementation 'com.android.support.test.espresso: espressokjerne: 3.0. 2 'androidTestImplementation' com.android.support.test: løper: 1.0.2 'androidTestImplementation' com.android.support.test: regler: 1.0.2 '

Vi har også inkludert instrumentasjonsløperen AndroidJUnitRunner:

en instrumentering som kjører JUnit3 og JUnit4 tester mot en Android-pakke (program).

Noter det instrumentering er rett og slett en grunnklass for implementering av applikasjonsinstrumentkoden. 

Slå av animasjon 

Synkroniseringen av Espresso, som ikke vet hvordan man venter på at en animasjon skal fullføres, kan føre til at noen tester mislykkes - hvis du tillater animasjon på testenheten din. For å slå av animasjon på testenheten, gå til innstillinger > Utviklermuligheter og slå av alle følgende alternativer under "Tegning" -delen: 

  • Vinduets animasjonsskala
  • Overgang animasjon skala
  • Animator varighet skala

5. Skriv din første prøve i Espresso

Først begynner vi å teste en påloggingsskjerm. Slik begynner påloggingsflyten: Brukeren starter appen, og det første skjermbildet som vises, inneholder en enkelt Logg Inn knapp. Når det Logg Inn knappen er klikket, den åpner opp LoginActivity skjerm. Denne skjermen inneholder bare to EditTexts (brukernavn og passordfelt) og a Sende inn knapp. 

Her er hva vår Hoved aktivitet Oppsettet ser ut som:

Her er hva vår LoginActivity Oppsettet ser ut som:

La oss nå skrive en test for vår Hoved aktivitet klasse. Gå til din Hoved aktivitet klasse, flytt markøren til Hoved aktivitet navn og trykk på Shift-Control-T. Å velge Opprett nytt test ... i popup-menyen. 

trykk OK knappen, og en annen dialog vises. Velg androidTest katalog og klikk på OK knappen en gang til. Merk at fordi vi skriver en instrumenteringstest (Android SDK-spesifikke tester), ligger testene i androidTest / java mappe. 

Nå har Android Studio opprettet en testklasse for oss. Over klassenavnet, ta med denne merknaden: @RunWith (AndroidJUnit4 :: klasse).

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

Denne merknaden betyr at alle tester i denne klassen er Android-spesifikke tester.

Testing Aktiviteter

Fordi vi vil teste en aktivitet, må vi gjøre et lite oppsett. Vi må informere Espresso hvilken aktivitet som skal åpnes eller startes før du utfører og ødelegger etter å ha utført en testmetode. 

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

Legg merke til at @Regel merknad betyr at dette er en JUnit4 testregel. JUnit4 testregler kjøres før og etter hver testmetode (merket med @Test). I vårt eget scenario ønsker vi å lansere Hoved aktivitet før hver testmetode og ødelegge den etterpå. 

Vi inkluderte også @JvmField Kotlin annotasjon. Dette instruerer rett og slett kompilatoren om ikke å generere getters og setters for eiendommen og i stedet for å avsløre den som et enkelt Java-felt.

Her er de tre viktigste trinnene i å skrive en Espresso-test:

  • Se etter widgeten (f.eks. TextView eller Knapp) du vil teste.
  • Utfør en eller flere handlinger på den widgeten. 
  • Bekreft eller sjekk for å se om den widgeten nå er i en bestemt tilstand. 

Følgende typer merknader kan brukes på metodene som brukes i testklassen.

  • @BeforeClass: Dette indikerer at den statiske metoden denne annotasjonen gjelder for, må utføres en gang før alle tester i klassen. Dette kan for eksempel brukes til å opprette en forbindelse til en database. 
  • @Før: indikerer at metoden denne annotasjonen er knyttet til må utføres før hver testmetode i klassen.
  • @Test: indikerer at metoden denne annotasjonen er festet til, skal kjøre som et testfall.
  • @Etter: Indikerer at metoden denne annotasjonen er festet til, bør løpe etter hver testmetode. 
  • @Etter timen: indikerer at metoden denne annotasjonen er knyttet til, bør løpe etter at alle testmetodene i klassen er kjørt. Her lukker vi vanligvis ut ressurser som ble åpnet i @BeforeClass

Finn en Utsikt Ved hjelp av onView ()

I vår Hoved aktivitet layoutfil, vi har bare en widget-the Logg Inn knapp. La oss teste et scenario der en bruker finner den knappen og klikker på den.

importer android.support.test.espresso.Espresso.onVise import android.support.test.espresso.matcher.ViewMatchers.withId // ... @RunWith (AndroidJUnit4 :: klasse) klasse MainActivityTest // ... @Test @Throws (Unntak: : klasse) moro clickLoginButton_opensLoginUi () onView (withId (R.id.btn_login))

For å finne widgets i Espresso benytter vi onView () statisk metode (i stedet for findViewById ()). Parameter typen vi leverer til onView () er en Matcher. Legg merke til at Matcher API kommer ikke fra Android SDK, men i stedet fra Hamcrest Project. Hamcrests matcherbibliotek er inne i Espressobiblioteket vi trakk via Gradle. 

De onView (withId (R.id.btn_login)) vil returnere a ViewInteraction det er for en Utsikt hvis ID er R.id.btn_login. I eksemplet ovenfor brukte vi withId () å lete etter en widget med et gitt ID. Andre visningskamper vi kan bruke er: 

  • withText (): returnerer en matcher som samsvarer med TextView basert på tekstverdiens verdi.
  • withHint (): returnerer en matcher som samsvarer med TextView basert på hint eiendomsverdi.
  • withTagKey (): returnerer en matcher som samsvarer med Utsikt basert på tagnøkler.
  • withTagValue (): returnerer en matcher som samsvarer med Utsikts basert på tagegenskapsverdier.

Først, la oss teste for å se om knappen faktisk vises på skjermen. 

onView (withId (R.id.btn_login)). sjekk (fyrstikker (isDisplayed ()))

Her bekrefter vi bare om knappen med gitt ID (R.id.btn_login) er synlig for brukeren, så vi bruker kryss av() metode for å bekrefte om den underliggende Utsikt har en viss tilstand - i vårt tilfelle, hvis den er synlig.

De fyrstikker() statisk metode returnerer en generisk ViewAssertion som hevder at en visning eksisterer i visningshierarkiet og matches av den gitte visningskamperen. Den gitte visningskamperen returneres ved å ringe er vist(). Som foreslått av metodenavnet, er vist() er en matcher som matcher Utsikts som nå vises på skjermen til brukeren. For eksempel, hvis vi vil sjekke om en knapp er aktivert, sender vi rett og slett Er på() til fyrstikker()

Andre populære visningskamper vi kan passere inn i fyrstikker() metoden er:

  • hasFocus (): returnerer en matcher som samsvarer med Utsikts som for øyeblikket har fokus.
  • isChecked (): returnerer en matcher som aksepterer hvis og bare hvis visningen er en CompoundButton (eller undertype av) og er i kontrollert tilstand. Det motsatte av denne metoden er isNotChecked ()
  • isSelected (): returnerer en matcher som samsvarer med Utsikts som er valgt.

For å kjøre testen kan du klikke på den grønne trekant ved siden av metoden eller klassenavnet. Hvis du klikker på den grønne trekant ved siden av klassenavnet, kjøres alle testmetodene i den klassen, mens den ene ved siden av en metode vil kjøre testen bare for den metoden. 

Hurra! Vår test besto!


Utfør handlinger på en visning

På en ViewInteraction objekt som returneres ved å ringe onView (), Vi kan simulere handlinger en bruker kan utføre på en widget. For eksempel kan vi simulere en klikkhandling ved å bare ringe til Klikk () statisk metode inne i ViewActions klasse. Dette vil returnere a ViewAction objekt for oss. 

Dokumentasjonen sier at ViewAction er:

Ansvarlig for å utføre en interaksjon på det gitte visningselementet.
@Test morsomt clickLoginButton_opensLoginUi () // ... onView (withId (R.id.btn_login)). Utfør (klikk ())

Vi utfører en klikkhendelse ved første anrop utføre(). Denne metoden utfører den gjeldende handlingen (e) på visningen som er valgt av den nåværende visningskonkurrenten. Legg merke til at vi kan sende den en enkelt handling eller en liste over handlinger (utført i rekkefølge). Her ga vi det Klikk (). Andre mulige tiltak er:

  • typeText () å imitere skrive inn tekst i en EditText.
  • klartekst() å simulere rydding av tekst i en EditText.
  • Dobbeltklikk() å simulere dobbeltklikk på a Utsikt.
  • longClick () å etterligne lang-klikking a Utsikt.
  • scrollTo () å simulere rulling a ScrollView til en bestemt Utsikt det er synlig. 
  • swipeLeft () å simulere sveiping rett til venstre over det vertikale sentrum av a Utsikt.

Mange flere simuleringer finnes i ViewActions klasse. 

Bekreft med visningsannonser

La oss fullføre testen, for å validere at LoginActivity skjermen vises når Logg Inn knappen er klikket. Selv om vi allerede har sett hvordan du bruker kryss av() på en ViewInteraction, la oss bruke det igjen, passere det en annen ViewAssertion

@Test morsomt clickLoginButton_opensLoginUi () // ... onView (withId (R.id.tv_login)). Sjekk (kamper (isDisplayed ()))

Inne i LoginActivity layoutfil, bortsett fra EditTexts og a Knapp, vi har også a TextView med ID R.id.tv_login. Så vi gjør bare en sjekk for å bekrefte at TextView er synlig for brukeren. 

Nå kan du kjøre testen igjen!

Dine tester bør passere vellykket hvis du fulgte alle trinnene riktig. 

Her er det som skjedde under prosessen med å utføre testene våre: 

  1. Lanserte Hoved aktivitet bruker activityRule felt.
  2. Verifisert hvis Logg Inn knapp (R.id.btn_login) var synlig (er vist()) til brukeren.
  3. Simulert en klikkhandling (Klikk ()) på den knappen.
  4. Verifisert hvis LoginActivity ble vist til brukeren ved å sjekke om a TextView med id R.id.tv_login i LoginActivity er synlig.

Du kan alltid referere til Espresso-cheatarket for å se de forskjellige visningskamrene, vise handlinger og se påstandene som er tilgjengelige. 

6. Test på LoginActivity Skjerm

Her er vår LoginActivity.kt:

importer android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Button import android.widget.EditText import android.widget.TextView klasse LoginActivity: AppCompatActivity () private lateinit var brukernavnEditText: EditText private sentinit var loginTitleTextView: TextView Private Sentinit var passwordEditText: EditText Private Lateinit var submitButton: Knapp overstyrer moro onCreate (savedInstanceState: Bundle?) super.onCreate (savedInstanceState) setContentView (R.layout.activity_login) brukernavnEditText = 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 (brukernavnEditText.text.toString () == "chike" && passwordEditText. text.toString () == "passord") loginTitleTextView.text = "Suksess" annet loginTitleTextView.text = "Feil"

I koden ovenfor, hvis det oppgitte brukernavnet er "chike" og passordet er "passord", er innloggingen vellykket. For andre innganger er det en feil. La oss nå skrive en Espresso-test for dette!

Gå til LoginActivity.kt, flytte markøren til LoginActivity navn og trykk på Shift-Control-T. Å velge Opprett nytt test ...  i popup-menyen. Følg samme prosess som vi gjorde for MainActivity.kt, og klikk på OK knapp. 

importer android.support.test.espresso.Espresso import android.support.test.espresso.Espresso.onVise import android.support.test.espresso.action.ViewActions importer android.support.test.espresso.assertion.ViewAssertions.matches import android .support.test.espresso.matcher.ViewMatchers.withId importerer android.support.test.espresso.matcher.ViewMatchers.withText import android.support.test.rule.ActivityTestRule importere android.support.test.runner.AndroidJUnit4 import org.junit .Rule import org.junit.Test import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: klasse) klasse LoginActivityTest @Rule @JvmField var activityRule = ActivityTestRule(LoginActivity :: class.java) privat val brukernavn = "chike" privat val passord = "passord" @Test morsomt clickLoginButton_opensLoginUi () onView (withId (R.id.et_username)) utføre (ViewActions.typeText (brukernavn)) onView (withId (R.id.et_password)) utføre (ViewActions.typeText (passord)) onView (withId (R.id.btn_submit)) utføre (ViewActions.scrollTo (), ViewActions.click ()) Espresso.onView (withId (R.id.tv_login)) .check (matcher (withText ("suksess")))

Denne testklassen ligner veldig vår første. Hvis vi kjører testen, vår LoginActivity skjermen er åpnet. Brukernavnet og passordet er skrevet inn i R.id.et_username og R.id.et_password felter henholdsvis. Deretter klikker Espresso på Sende inn knapp (R.id.btn_submit). Det vil vente til a Utsikt med id R.id.tv_login kan bli funnet med tekstavlesning Suksess

7. Test a RecyclerView

RecyclerViewActions er klassen som avslører et sett med APIer for å operere på en RecyclerView. RecyclerViewActions er en del av en egen artefakt inne i espresso-contrib artefakt, som også skal legges til build.gradle:

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

Merk at denne artefakten også inneholder API for brukergrensesnitt som tester navigasjonsskuffen gjennom DrawerActions og DrawerMatchers

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

Å klikke på et element på en hvilken som helst posisjon i en RecyclerView, vi påberoper actionOnItemAtPosition (). Vi må gi den en type gjenstand. I vårt tilfelle er varen den ViewHolder klasse inne i vår RandomAdapter. Denne metoden tar også i to parametere; den første er posisjonen, og den andre er handlingen (ViewActions.click ()). 

Annen RecyclerViewActions som kan utføres er:

  • actionOnHolderItem (): utfører a ViewAction på en visning som matches av viewHolderMatcher. Dette gjør at vi kan samsvare med det som finnes inne i ViewHolder heller enn stillingen. 
  • scrollToPosition (): returnerer a ViewAction som ruller RecyclerView til en posisjon.

Neste (når "Legg til notatskjermbildet" er åpent), vil vi skrive inn notattekst og lagre notatet. Vi trenger ikke å vente på den nye skjermen for å åpne. Espresso vil gjøre dette automatisk for oss. Det venter til en visning med id R.id.add_note_title kan bli funnet.

8. Testintensjoner

Espresso benytter seg av en annen artefakt som heter espresso-hensikter for testing hensikter. Denne gjenstanden er bare en ekstra utvidelse til Espresso som fokuserer på validering og mocking av Intents. La oss se på et eksempel.

Først må vi trekke espresso-hensikter bibliotek i vårt prosjekt. 

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

IntentsTestRule strekker ActivityTestRule, slik at de begge har lignende oppførsel. Her er hva doc sier:

Denne klassen er en forlengelse av ActivityTestRule, som initialiserer Espresso-Intents før hver test annotert med Test og gir ut Espresso-Intents etter hver testkjøring. Aktiviteten avsluttes etter hver test, og denne regelen kan brukes på samme måte som ActivityTestRule.

Den viktigste differensieringsfunksjonen er at den har flere funksjoner for testing startActivity () og startActivityForResult () med mocks og stubber. 

Vi skal nå teste et scenario der en bruker vil klikke på en knapp (R.id.btn_select_contact) på skjermen for å velge en kontakt fra telefonens kontaktliste. 

// ... @Test morsomt stubPick () var result = Instrumentation.ActivityResult (Activity.RESULT_OK, Intent (null, ContactsContract.Contacts.CONTENT_URI)) intending (hasAction (Intent.ACTION_PICK)) .respons (resultat) onView (withId R.id.btn_select_contact)). Utfør (klikk ()) beregnet (allOf (toPackage ("com.google.android.contacts"), hasAction (Intent.ACTION_PICK), hasData (ContactsContract.Contacts.CONTENT_URI)) // ...

Her bruker vi hensikt () fra espresso-hensikter bibliotek for å sette opp en stub med en mock respons for våre ACTION_PICK be om. Her er hva som skjer i  PickContactActivity.kt når brukeren klikker på knappen med id R.id.btn_select_contact å velge en kontakt.

morsom pickContact (v: View) val I = Intent (Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) startAktivitetForResultat (jeg, PICK_REQUEST)

hensikt () tar inn en Matcher som samsvarer med intentene for hvilke stubbet respons skal gis. Med andre ord, Matcher identifiserer hvilken forespørsel du er interessert i stubbing. I vårt eget tilfelle bruker vi hasAction () (en hjelpemetode i IntentMatchers) for å finne vår ACTION_PICK be om. Vi påberoper oss da respondWith (), som setter resultatet for onActivityResult (). I vårt tilfelle har resultatet Activity.RESULT_OK, simulerer brukeren å velge en kontakt fra listen. 

Vi simulerer deretter klikke på velg kontaktknappen, som ringer startActivityForResult (). Legg merke til at stubben vår sendte responsen til onActivityResult ()

Til slutt bruker vi beregnet () hjelpemetode for å bare validere det samtalene til startActivity () og startActivityForResult () ble laget med riktig informasjon. 

Konklusjon

I denne opplæringen lærte du hvordan du enkelt kan bruke Espresso-testing rammeverket i Android Studio-prosjektet for å automatisere test arbeidsflyten din. 

Jeg anbefaler på det sterkeste å sjekke ut den offisielle dokumentasjonen for å lære mer om å skrive UI-tester med Espresso.