Testa Android användargränssnitt med espresso

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.

1. Varför använda Espresso?

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:

  1. Starta appen. 
  2. Navigera till inloggningsskärmen. 
  3. Bekräfta om usernameEditText och passwordEditText är synliga. 
  4. Skriv användarnamnet och lösenordet i respektive fält. 
  5. Bekräfta om inloggningsknappen också är synlig och klicka sedan på den inloggningsknappen.
  6. Kontrollera om rätt visningar visas när den inloggningen lyckades eller var ett misslyckande. 

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:   

  • Automatiserade test utför exakt samma testfall varje gång de körs. 
  • Utvecklare kan snabbt upptäcka ett problem snabbt innan det skickas till QA-teamet. 
  • Det kan spara mycket tid, till skillnad från att manuell testning görs. Genom att spara tid kan programvarutekniker och QA-teamet i stället spendera mer tid på utmanande och givande uppgifter. 
  • Högre test täckning uppnås, vilket leder till en bättre kvalitet ansökan. 

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

2. Förutsättningar

För att kunna följa denna handledning behöver du:

  • en grundläggande förståelse för centrala Android API och Kotlin
  • Android Studio 3.1.3 eller senare
  • Kotlin plugin 1.2.51 eller högre

Ett provprojekt (i Kotlin) för denna handledning finns på vår GitHub repo så att du enkelt kan följa med.

3. Skapa ett Android Studio-projekt

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

4. Ställ in Espresso och 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. 

Stäng av animering 

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önster animering skala
  • Övergångs animationsskala
  • Animator varaktighet skala

5. Skriv ditt första test i espresso

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 texts (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.

Testaktiviteter

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:

  • Leta efter widgeten (t.ex.. Textview eller Knapp) du vill testa.
  • Utför en eller flera åtgärder på den widgeten. 
  • Verifiera eller kontrollera om den widgeten nu är i ett visst tillstånd. 

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

Hitta en 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 Ses 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 Ses 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 Ses 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 Ses 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!


Utför åtgärder på en vy

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. 

Validera med visningsansökningar

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 texts 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: 

  1. Lanserade Huvudaktivitet använda activityRule fält.
  2. Verifierad om Logga in knapp (R.id.btn_login) var synlig (isDisplayed ()) till användaren.
  3. Simulerat en klickåtgärd (klick()) på den knappen.
  4. Verifierad om 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. 

6. Testa LoginActivity Skärm

Hä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

7. Testa a 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.

8. Testintentioner

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 av ActivityTestRule, som initierar Espresso-Intents före varje test som antecknas med Testa 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 som ActivityTestRule.

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. 

Slutsats

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.