I det här inlägget lär du dig hur du skriver UI-tester med Espresso-testramen och automatiserar ditt test-arbetsflöde istället för att använda den tråkiga och mycket felaktiga manuella processen.
Espresso är ett testramverk för att skriva UI-test i Android. Enligt de officiella dokumenten kan du:
Använd Espresso för att skriva korta, vackra och tillförlitliga Android UI-test.
Ett av problemen med manuell provning är att det kan vara tidskrävande och tråkigt att utföra. Om du till exempel vill testa en inloggningsskärm (manuellt) i en Android-app måste du göra följande:
usernameEditText
och passwordEditText
är synliga. I stället för att spendera hela tiden, testar vi vår app manuellt, det vore bättre att spendera mer tidskod som gör att appen stannar från resten! Och även om manuell provning är tråkig och ganska långsam, är det fortfarande felaktigt och du kanske saknar några hörnfall.
Några av fördelarna med automatiserad testning inkluderar följande:
I denna handledning lär vi oss om Espresso genom att integrera den i ett Android Studio-projekt. Vi skriver UI-tester för en inloggningsskärm och a RecyclerView
, och vi lär oss om testintentier.
Kvalitet är inte en handling, det är en vana. - Pablo Picasso
För att kunna följa denna handledning behöver du:
Ett provprojekt (i Kotlin) för denna handledning finns på vår GitHub repo så att du enkelt kan följa med.
Slå på din Android Studio 3 och skapa ett nytt projekt med en tom aktivitet som heter Huvudaktivitet
. Se till att kolla Inkludera Kotlin support.
AndroidJUnitRunner
När du har skapat ett nytt projekt, se till att du lägger till följande beroenden från Android Testing Support Library i din build.gradle (även om Android Studio redan har inkluderat dem för oss). I den här handledningen använder vi den senaste Espresso-biblioteksversionen 3.0.2 (som det här skrivet).
android // ... defaultConfig // ... testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // ... beroenden // ... androidTestImplementation 'com.android.support.test.espresso: espressokärna: 3.0. 2 'androidTestImplementation' com.android.support.test: löpare: 1.0.2 'androidTestImplementation' com.android.support.test: regler: 1.0.2 '
Vi inkluderade även instrumentinstrumentet AndroidJUnitRunner
:
En Instrumentation
som kör JUnit3 och JUnit4 tester mot ett Android-paket (ansökan).
Anteckna det Instrumentation
är helt enkelt en basklass för implementering av applikationsinstrumentkod.
Synkroniseringen av Espresso, som inte vet hur man väntar på att en animation ska slutföra, kan orsaka att vissa tester misslyckas - om du tillåter animering på din testenhet. För att stänga av animering på din testenhet, gå till inställningar > Utvecklaralternativ och stäng av alla följande alternativ under avsnittet "Ritning":
Först börjar vi testa en inloggningsskärm. Så här loggar in flödet: användaren startar appen och den första skärmen som visas innehåller en enda Logga in knapp. När det Logga in knappen klickas, den öppnar upp LoginActivity
skärm. Den här skärmen innehåller bara två Redigera text
s (fältet användarnamn och lösenord) och a Lämna knapp.
Här är vad vår Huvudaktivitet
layouten ser ut som:
Här är vad vår LoginActivity
layouten ser ut som:
Låt oss nu skriva ett test för vårt Huvudaktivitet
klass. Gå till din Huvudaktivitet
klass, flytta markören till Huvudaktivitet
namn och tryck på Shift-Control-T. Välj Skapa nytt test ... i popup-menyn.
tryck på ok knappen och en annan dialog visas. Välj den androidTest katalog och klicka på ok knappen en gång till. Observera att eftersom vi skriver ett instrumentationstest (Android SDK-specifika test) ligger testfallen i androidTest / java mapp.
Nu har Android Studio framgångsrikt skapat en testklass för oss. Ovanför klassnamnet, inkludera denna anteckning: @RunWith (AndroidJUnit4 :: klass)
.
importera android.support.test.runner.AndroidJUnit4 importera org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) class MainActivityTest
Denna anmärkning innebär att alla tester i den här klassen är Android-specifika tester.
Eftersom vi vill testa en aktivitet måste vi göra en liten installation. Vi behöver informera Espresso vilken aktivitet som ska öppnas eller startas innan körning och förstörelse efter att ha genomfört någon testmetod.
importera android.support.test.rule.ActivityTestRule importera 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)
Observera att @Regel
Anmärkning innebär att detta är en JUnit4 testregel. JUnit4 testregler körs före och efter varje testmetod (annoteras med @Testa
). I vårt eget scenario vill vi starta Huvudaktivitet
före varje testmetod och förstör den efter.
Vi inkluderade också @JvmField
Kotlin-anteckning. Detta instruerar helt enkelt kompilatorn att inte skapa getters och setters för egenskapen och istället att avslöja det som ett enkelt Java-fält.
Här är de tre stora stegen för att skriva ett Espresso-test:
Textview
eller Knapp
) du vill testa.Följande typer av noteringar kan tillämpas på de metoder som används inom testklassen.
@Innan lektionen
: Detta indikerar att den statiska metoden som denna anteckning tillämpas på måste utföras en gång och före alla tester i klassen. Det kan till exempel användas för att upprätta en anslutning till en databas. @Innan
: anger att metoden som denna anteckning är kopplad till måste utföras före varje testmetod i klassen.@Testa
: indikerar att metoden den här anteckningen är kopplad till ska fungera som ett testfall.@Efter
: anger att metoden den här annoteringen är kopplad till ska köras efter varje testmetod. @Efter lektionen
: Anger att metoden som denna anteckning är kopplad till ska köras efter att alla testmetoder i klassen har körts. Här stänger vi vanligtvis resurser som öppnades i @Innan lektionen
. Se
Använder sig av OnView ()
I vår Huvudaktivitet
layoutfil, vi har bara en widget-the Logga in
knapp. Låt oss testa ett scenario där en användare kommer att hitta den knappen och klicka på den.
importera android.support.test.espresso.Espresso.onVisa import android.support.test.espresso.matcher.ViewMatchers.withId // ... @RunWith (AndroidJUnit4 :: class) class MainActivityTest // ... @Test @Throws (Undantag: : klass) roligt clickLoginButton_opensLoginUi () onView (withId (R.id.btn_login))
För att hitta widgets i Espresso använder vi oss av OnView ()
statisk metod (istället för findViewById ()
). Den parametertyp vi levererar till OnView ()
är en Matcher
. Observera att Matcher
API kommer inte från Android SDK men istället från Hamcrest Project. Hamcrests matcherbibliotek finns inne i Espressobiblioteket som vi drog via Gradle.
De OnView (withId (R.id.btn_login))
kommer att returnera a ViewInteraction
det är för en Se
vars ID är R.id.btn_login
. I exemplet ovan använde vi withId ()
att leta efter en widget med ett visst id. Andra synkronisatorer som vi kan använda är:
withText ()
: returnerar en matchare som matchar Textview
baserat på dess text egendom värde.withHint ()
: returnerar en matchare som matchar Textview
baserat på dess hint fastighetsvärde.withTagKey ()
: returnerar en matchare som matchar Se
baserat på taggnycklar.withTagValue ()
: returnerar en matchare som matchar Se
s baserat på värden för tagegenskaper.Låt oss först testa för att se om knappen faktiskt visas på skärmen.
OnView (withId (R.id.btn_login)). kontrollera (tändstickor (isDisplayed ()))
Här bekräftar vi bara om knappen med det angivna id (R.id.btn_login
) är synlig för användaren, så vi använder kontrollera()
metod för att bekräfta om det underliggande Se
har ett visst tillstånd - i vårt fall, om det är synligt.
De tändstickor()
statisk metod returnerar en generisk ViewAssertion
som hävdar att en vy existerar i vyhierarkin och matchas av den givna vyskompatorn. Den givna synkroniseraren returneras genom att ringa isDisplayed ()
. Som föreslagits av metodnamnet, isDisplayed ()
är en matchare som matchar Se
s som för närvarande visas på skärmen för användaren. Om vi till exempel vill kontrollera om en knapp är aktiverad skickar vi helt enkelt isEnabled ()
till tändstickor()
.
Andra populära visa matchare vi kan passera in i tändstickor()
metoden är:
hasFocus ()
: returnerar en matchare som matchar Se
s som för närvarande har fokus.isChecked ()
: returnerar en matchare som accepterar om och endast om vyn är a CompoundButton
(eller undertyp av) och är i kontrollerat tillstånd. Det motsatta av denna metod är isNotChecked ()
. är vald()
: returnerar en matchare som matchar Se
s som väljs.För att köra testet kan du klicka på den gröna triangeln bredvid metoden eller klassnamnet. Om du klickar på den gröna triangeln bredvid klassnamnet körs alla testmetoder i den klassen, medan den bredvid en metod kör testet endast för den metoden.
Hurra! Vårt test passerade!
På en ViewInteraction
objekt som returneras genom att ringa OnView ()
, Vi kan simulera åtgärder en användare kan utföra på en widget. Till exempel kan vi simulera en klickåtgärd genom att helt enkelt ringa klick()
statisk metod inuti ViewActions
klass. Detta kommer att returnera a ViewAction
föremål för oss.
Dokumentationen säger att ViewAction
är:
Ansvarig för att utföra en interaktion på det visade visningselementet.
@Test rolig klickaLoginButton_opensLoginUi () // ... onView (withId (R.id.btn_login)). Utför (klicka ())
Vi utför ett klickhändelse genom att ringa första gången utföra()
. Den här metoden utför den angivna åtgärden i den vy som valdes av den aktuella vyskämparen. Observera att vi kan skicka den en enda åtgärd eller en lista över åtgärder (utförd i ordning). Här gav vi det klick()
. Andra möjliga åtgärder är:
typeText ()
att imitera skriva text till en Redigera text
.klar text()
för att simulera rensning av text i en Redigera text
.dubbelklicka()
för att simulera dubbelklicka på a Se
.longClick ()
för att imitera länge på a Se
.scrollTo ()
för att simulera rullning a ScrollView
till en viss Se
det är synligt. dra vänster()
att simulera swiping höger till vänster över det vertikala centrum av a Se
.Många fler simuleringar finns inom ViewActions
klass.
Låt oss slutföra vårt test för att validera det LoginActivity
skärmen visas när Logga in knappen klickas. Även om vi redan har sett hur man använder kontrollera()
på en ViewInteraction
, låt oss använda det igen och ge det ett annat ViewAssertion
.
@Test rolig klickaLoginButton_opensLoginUi () // ... onView (withId (R.id.tv_login)). Check (matcher (isDisplayed ()))
Inuti LoginActivity
layoutfil, bortsett från Redigera text
s och a Knapp
, vi har också a Textview
med ID R.id.tv_login
. Så vi gör helt enkelt en check för att bekräfta att Textview
är synlig för användaren.
Nu kan du köra provet igen!
Dina test ska passera om du följde alla steg korrekt.
Här är vad som hände under processen att utföra våra test:
Huvudaktivitet
använda activityRule
fält.R.id.btn_login
) var synlig (isDisplayed ()
) till användaren.klick()
) på den knappen.LoginActivity
visades till användaren genom att kontrollera om a Textview
med id R.id.tv_login
i LoginActivity
är synlig.Du kan alltid referera till Espresso cheat sheet för att se olika vy matchare, visa åtgärder och visa påståenden tillgängliga.
LoginActivity
SkärmHär är vår LoginActivity.kt:
importera android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Button import android.widget.EditText importera android.widget.TextView class LoginActivity: AppCompatActivity () privat lateinit var användarnamnEditText: EditText private lateinit var loginTitleTextView: TextView privat lateinit var passwordEditText: EditText privat lateinit var submitButton: Knappöverstyrning roligt onCreate (savedInstanceState: Bundle?) super.onCreate (savedInstanceState) setContentView (R.layout.activity_login) användarnamnEditText = 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 (användarnamnEditText.text.toString () == "chike" && passwordEditText. text.toString () == "lösenord") loginTitleTextView.text = "Framgång" annat loginTitleTextView.text = "Fel"
I koden ovan, om det inloggade användarnamnet är "chike" och lösenordet är "lösenord", så är inloggningen framgångsrik. För någon annan ingång är det ett misslyckande. Låt oss nu skriva ett Espresso-test för detta!
Gå till LoginActivity.kt, flytta markören till LoginActivity
namn och tryck på Shift-Control-T. Välj Skapa nytt test ... i popup-menyn. Följ samma process som vi gjorde för MainActivity.kt, och klicka på ok knapp.
importera android.support.test.espresso.Espresso importera android.support.test.espresso.Espresso.onVisa import android.support.test.espresso.action.ViewActions importera android.support.test.espresso.assertion.ViewAssertions.matches importera android .support.test.espresso.matcher.ViewMatchers.withId importerar android.support.test.espresso.matcher.ViewMatchers.withText import android.support.test.rule.ActivityTestRule importera 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(Logga inAktivitet :: class.java) privat val användarnamn = "chike" privat val lösenord = "lösenord" @Test rolig klickaLoginButton_opensLoginUi () onView (medId (R.id.et_username)) utföra (VisaActions.typeText (användarnamn)) onView (withId (R.id.et_password)) utföra (ViewActions.typeText (lösenord)) onView (withId (R.id.btn_submit)) utföra (ViewActions.scrollTo (), ViewActions.click ()) Espresso.onView (withId (R.id.tv_login)) .check (matchar (withText ("Success")))
Denna testklass är mycket lik vår första. Om vi kör testet, vår LoginActivity
skärmen öppnas. Användarnamnet och lösenordet skrivs in i R.id.et_username
och R.id.et_password
fält respektive Nästa kommer Espresso att klicka på Lämna knapp (R.id.btn_submit
). Det kommer att vänta tills a Se
med id R.id.tv_login
kan hittas med läsning av text Framgång.
RecyclerView
RecyclerViewActions
är den klass som avslöjar en uppsättning API för att fungera på en RecyclerView
. RecyclerViewActions
är en del av en separat artefakt inuti espresso-contrib
artefakt, som också bör läggas till build.gradle:
androidTestImplementation 'com.android.support.test.espresso: espresso-bidrag: 3.0.2'
Observera att denna artefakt också innehåller API för gränssnittskonfiguration som testar navigationslådan genom DrawerActions
och DrawerMatchers
.
@RunWith (AndroidJUnit4 :: klass) klass MyListActivityTest // ... @Test kul clickItem () onView (withId (R.id.rv)) .perform (RecyclerViewActions .actionOnItemAtPosition(0, ViewActions.click ()))
Att klicka på ett objekt på vilken position som helst i en RecyclerView
, vi åberopar actionOnItemAtPosition ()
. Vi måste ge den en typ av föremål. I vårt fall är objektet det ViewHolder
klass inne i vår RandomAdapter
. Denna metod tar också in två parametrar; den första är positionen och den andra är åtgärden (ViewActions.click ()
).
Andra RecyclerViewActions
som kan utföras är:
actionOnHolderItem ()
: utför a ViewAction
på en vy som matchas av viewHolderMatcher
. Detta gör det möjligt för oss att matcha det med vad som finns inne i ViewHolder
snarare än positionen. scrollToPosition ()
: returnerar a ViewAction
som rullar RecyclerView
till en position.Nästa (när "Add Note Screen" är öppen), kommer vi att ange vår anteckningstekst och spara anteckningen. Vi behöver inte vänta på att den nya skärmen öppnas. Espresso gör det automatiskt för oss. Det väntar tills en vy med id R.id.add_note_title
kan hittas.
Espresso använder sig av en annan artefakt som heter espresso-avsikter
för test avsikter. Denna artefakt är bara en annan förlängning till Espresso som fokuserar på validering och mocking av Intents. Låt oss titta på ett exempel.
Först måste vi dra espresso-avsikter
biblioteket i vårt projekt.
androidTestImplementation 'com.android.support.test.espresso: espresso-intents: 3.0.2'
importera android.support.test.espresso.intent.rule.IntentsTestRule importera android.support.test.runner.AndroidJUnit4 import org.junit.Rule import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: klass) klass PickContactActivityTest @ Regel @JvmField var intentRule = IntentsTestRule(PickContactActivity :: class.java)
IntentsTestRule
sträcker ActivityTestRule
, så de båda har liknande beteenden. Här är vad docen säger:
Denna klass är en förlängning avActivityTestRule
, som initierar Espresso-Intents före varje test som antecknas medTesta
och släpper ut Espresso-Intents efter varje provkörning. Aktiviteten avslutas efter varje test och denna regel kan användas på samma sätt somActivityTestRule
.
Den viktigaste differentieringsfunktionen är att den har ytterligare funktioner för testning startActivity ()
och startActivityForResult ()
med mocks och stubbar.
Vi ska nu testa ett scenario där en användare klickar på en knapp (R.id.btn_select_contact
) på skärmen för att välja en kontakt från telefonens kontaktlista.
// ... @Test fun stubPick () var result = Instrumentation.ActivityResult (Aktivitet.RESULT_OK, Intent (null, ContactsContract.Contacts.CONTENT_URI)) Intending (hasAction (Intent.ACTION_PICK)). Svara med (resultat) onView (withId R.id.btn_select_contact)). Utför (klicka ()) avsedd (allOf (toPackage ("com.google.android.contacts"), hasAction (Intent.ACTION_PICK), hasData (ContactsContract.Contacts.CONTENT_URI)) ...
Här använder vi avser ()
från espresso-avsikter
bibliotek för att ställa upp en stubbe med ett mock svar för vår ACTION_PICK
begäran. Här är vad som händer i PickContactActivity.kt när användaren klickar på knappen med id R.id.btn_select_contact
att välja en kontakt.
kul pickContact (v: View) val I = Intent (Intent.ACTION_PICK, KontakterContract.Contacts.CONTENT_URI) startAktivitetForResultat (jag, PICK_REQUEST)
avser ()
tar in a Matcher
som matchar intent för vilket stubbed svar ska ges. Med andra ord, Matcher
identifierar vilken förfrågan du är intresserad av stubbing. I vårt eget fall använder vi oss av hasAction ()
(en hjälparmetod i IntentMatchers
) för att hitta vårt ACTION_PICK
begäran. Vi åberopar sedan respondWith ()
, vilket sätter resultatet för onActivityResult ()
. I vårt fall har resultatet Activity.RESULT_OK
, simulerar användaren väljer en kontakt från listan.
Vi simulerar sedan på knappen välj kontakt, som ringer startActivityForResult ()
. Observera att vår stub skickade det svåra svaret till onActivityResult ()
.
Slutligen använder vi avsedd()
hjälparmetod för att bara validera det samtalet till startActivity ()
och startActivityForResult ()
gjordes med rätt information.
I den här handledningen lärde du dig hur du enkelt använder Espresso-testramen i ditt Android Studio-projekt för att automatisera ditt test-arbetsflöde.
Jag rekommenderar starkt att du kolla in den officiella dokumentationen för att lära dig mer om att skriva UI-tester med Espresso.