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.
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:
usernameEditText
og passwordEditText
er synlige. 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:
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
For å kunne følge denne opplæringen må du:
Et prøveprosjekt (i Kotlin) for denne opplæringen finner du på vår GitHub repo, slik at du enkelt kan følge med.
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.
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.
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:
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 EditText
s (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.
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:
TextView
eller Knapp
) du vil teste.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
. 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 Utsikt
s 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 Utsikt
s 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 Utsikt
s 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 Utsikt
s 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!
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.
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 EditText
s 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:
Hoved aktivitet
bruker activityRule
felt.R.id.btn_login
) var synlig (er vist()
) til brukeren.Klikk ()
) på den knappen.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.
LoginActivity
SkjermHer 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.
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.
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 avActivityTestRule
, som initialiserer Espresso-Intents før hver test annotert medTest
og gir ut Espresso-Intents etter hver testkjøring. Aktiviteten avsluttes etter hver test, og denne regelen kan brukes på samme måte somActivityTestRule
.
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.
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.