In dit bericht leert u hoe u UI-tests kunt schrijven met het Espresso-testraamwerk en uw testworkflow kunt automatiseren in plaats van het saaie en zeer foutgevoelige handmatige proces te gebruiken.
Espresso is een testraamwerk voor het schrijven van UI-tests in Android. Volgens de officiële documenten, kunt u:
Gebruik Espresso om beknopte, mooie en betrouwbare Android UI-tests te schrijven.
Een van de problemen bij handmatig testen is dat het tijdrovend en vervelend kan zijn om te presteren. Als u bijvoorbeeld een inlogscherm (handmatig) in een Android-app wilt testen, moet u het volgende doen:
usernameEditText
en passwordEditText
zijn zichtbaar. In plaats van al onze tijd te besteden aan het handmatig testen van onze app, zou het beter zijn om meer tijd te besteden aan het schrijven van code waardoor onze app zich onderscheidt van de rest! En hoewel handmatig testen vervelend en vrij langzaam is, is het nog steeds foutgevoelig en mist u enkele hoekgevallen.
Enkele van de voordelen van geautomatiseerd testen omvatten het volgende:
In deze zelfstudie leren we meer over Espresso door het te integreren in een Android Studio-project. We zullen UI-tests schrijven voor een inlogscherm en een RecyclerView
, en we zullen leren over het testen van intenties.
Kwaliteit is geen handeling, het is een gewoonte. - Pablo Picasso
Als je deze zelfstudie wilt kunnen volgen, heb je het volgende nodig:
Een voorbeeldproject (in Kotlin) voor deze tutorial is te vinden op onze GitHub repo, zodat je gemakkelijk kunt volgen.
Start je Android Studio 3 op en maak een nieuw project met een lege activiteit genaamd Hoofdactiviteit
. Zorg ervoor om te controleren Inclusief Kotlin-ondersteuning.
AndroidJUnitRunner
Nadat u een nieuw project hebt gemaakt, moet u ervoor zorgen dat u de volgende afhankelijkheden van de ondersteuningsbibliotheek Android Test in uw build.gradle (hoewel Android Studio deze al voor ons heeft opgenomen). In deze zelfstudie gebruiken we de nieuwste Espresso-bibliotheekversie 3.0.2 (vanaf dit schrijven).
android // ... defaultConfig // ... testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // ... afhankelijkheden // ... androidTestImplementation 'com.android.support.test.espresso: espresso-core: 3.0. 2 'androidTestImplementation' com.android.support.test: runner: 1.0.2 'androidTestImplementation' com.android.support.test: rules: 1.0.2 '
We hebben ook de instrumentatierunner opgenomen AndroidJUnitRunner
:
Een Instrumentatie
die JUnit3- en JUnit4-tests uitvoert tegen een Android-pakket (toepassing).
Let daar op Instrumentatie
is gewoon een basisklasse voor het implementeren van applicatie-instrumentatiecode.
De synchronisatie van Espresso, die niet weet hoe te wachten tot een animatie is voltooid, kan ervoor zorgen dat sommige tests mislukken, als u animaties op uw testapparaat toestaat. Als u de animatie op uw testapparaat wilt uitschakelen, gaat u naar instellingen > Ontwikkelaarsopties en schakel alle volgende opties uit onder het gedeelte "Tekenen":
Eerst beginnen we met het testen van een aanmeldscherm. Zo begint de login-stroom: de gebruiker start de app en het eerste getoonde scherm bevat een single Log in knop. Wanneer dat Log in knop wordt geklikt, wordt de LoginActivity
scherm. Dit scherm bevat slechts twee Tekst bewerken
s (de velden gebruikersnaam en wachtwoord) en a voorleggen knop.
Dit is wat onze Hoofdactiviteit
lay-out ziet eruit als:
Dit is wat onze LoginActivity
lay-out ziet eruit als:
Laten we nu een test schrijven voor onze Hoofdactiviteit
klasse. Ga naar je Hoofdactiviteit
klasse, verplaats de cursor naar de Hoofdactiviteit
naam en druk op Shift-Control-T. kiezen Maak een nieuwe test ... in het pop-upmenu.
druk de OK knop en er verschijnt een nieuw dialoogvenster. Kies de androidTest map en klik op de OK knop nog een keer. Merk op dat, omdat we een instrumentatietest schrijven (specifieke Android SDK-tests), de testcases zich in de androidTest / java map.
Nu heeft Android Studio met succes een testklasse voor ons gemaakt. Boven de klassenaam neemt u deze annotatie op: @RunWith (AndroidJUnit4 :: klasse)
.
importeer android.support.test.runner.AndroidJUnit4 import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) class MainActivityTest
Deze annotatie betekent dat alle tests in deze klasse Android-specifieke tests zijn.
Omdat we een activiteit willen testen, moeten we een beetje instellen. We moeten Espresso informeren welke Activiteit moet worden geopend of gestart voordat deze wordt uitgevoerd en vernietigd na het uitvoeren van een testmethode.
import android.support.test.rule.ActivityTestRule import 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)
Merk op dat de @Regel
annotatie betekent dat dit een JUnit4-testregel is. JUnit4-testregels worden voor en na elke testmethode uitgevoerd (geannoteerd met @Test
). In ons eigen scenario willen we starten Hoofdactiviteit
vóór elke testmethode en vernietig het daarna.
We hebben ook de @JvmField
Annotatie van Kotlin. Dit geeft de compiler eenvoudig de opdracht om geen getters en setters voor de eigenschap te genereren en deze in plaats daarvan bloot te stellen als een eenvoudig Java-veld.
Dit zijn de drie belangrijkste stappen bij het schrijven van een Espresso-test:
Tekstweergave
of Knop
) die je wilt testen.De volgende typen annotaties kunnen worden toegepast op de methoden die in de testklasse worden gebruikt.
@Voor klas
: dit geeft aan dat de statische methode waarop deze annotatie wordt toegepast, één keer moet worden uitgevoerd en vóór alle tests in de klas. Dit kan bijvoorbeeld worden gebruikt om een verbinding met een database tot stand te brengen. @Voor
: geeft aan dat de methode waaraan deze annotatie is gekoppeld moet worden uitgevoerd vóór elke testmethode in de klas.@Test
: geeft aan dat de methode waaraan deze annotatie is gekoppeld, als een testcase moet worden uitgevoerd.@Na
: geeft aan dat de methode waaraan deze annotatie is gekoppeld, na elke testmethode moet worden uitgevoerd. @Na de les
: geeft aan dat de methode waaraan deze annotatie is gekoppeld, moet worden uitgevoerd nadat alle testmethoden in de klasse zijn uitgevoerd. Hier sluiten we doorgaans bronnen af die werden geopend @Voor klas
. Uitzicht
Gebruik makend van OnView ()
In onze Hoofdactiviteit
lay-outbestand, we hebben slechts één widget-de Log in
knop. Laten we een scenario testen waarbij een gebruiker die knop vindt en erop klikt.
import android.support.test.espresso.Espresso.onView import android.support.test.espresso.matcher.ViewMatchers.withId // ... @RunWith (AndroidJUnit4 :: class) class MainActivityTest // ... @Test @Throws (Uitzondering: : class) fun clickLoginButton_opensLoginUi () onView (withId (R.id.btn_login))
Om widgets in Espresso te vinden, maken we gebruik van de OnView ()
statische methode (in plaats van findViewById ()
). Het parametertype dat we leveren OnView ()
is een Matcher
. Merk op dat de Matcher
API komt niet van de Android SDK maar van het Hamcrest-project. De matcher-bibliotheek van Hamcrest bevindt zich in de Espresso-bibliotheek die we via Gradle hebben getrokken.
De OnView (withId (R.id.btn_login))
zal terugkeren a ViewInteraction
dat is voor een Uitzicht
wiens ID is R.id.btn_login
. In het bovenstaande voorbeeld hebben we gebruikt withId ()
om een widget met een bepaald ID te zoeken. Andere view matchers die we kunnen gebruiken zijn:
withText ()
: retourneert een matcher die overeenkomt Tekstweergave
op basis van de waarde van de teksteigenschap.withHint ()
: retourneert een matcher die overeenkomt Tekstweergave
op basis van de eigenschapwaarde van de hint.withTagKey ()
: retourneert een matcher die overeenkomt Uitzicht
op basis van tag-sleutels.withTagValue ()
: retourneert een matcher die overeenkomt Uitzicht
s gebaseerd op tageigenschapswaarden.Laten we eerst testen of de knop daadwerkelijk op het scherm wordt weergegeven.
OnView (withId (R.id.btn_login)). te controleren (wedstrijden (isDisplayed ()))
Hier bevestigen we alleen maar of de knop met de gegeven id (R.id.btn_login
) is zichtbaar voor de gebruiker, dus gebruiken we de controleren()
methode om te bevestigen als het onderliggende Uitzicht
heeft een bepaalde staat - in ons geval, als het zichtbaar is.
De wedstrijden()
statische methode levert een generiek op ViewAssertion
die beweert dat er een weergave bestaat in de weergavehiërarchie en dat deze wordt gematcht door de gegeven weergavezoekmachine. Die bepaalde view matcher wordt geretourneerd door te bellen is weergegeven()
. Zoals gesuggereerd door de methode naam, is weergegeven()
is een matcher die overeenkomt Uitzicht
s die momenteel op het scherm worden weergegeven voor de gebruiker. Als we bijvoorbeeld willen controleren of een knop is ingeschakeld, gaan we gewoon door is ingeschakeld()
naar wedstrijden()
.
Andere populaire view matchers die we kunnen doorgeven aan de wedstrijden()
methode zijn:
hasFocus ()
: retourneert een matcher die overeenkomt Uitzicht
s die momenteel focus hebben.is nagekeken()
: retourneert een matcher die alleen accepteert als de weergave een is CompoundButton
(of een subtype van) en bevindt zich in de gecontroleerde staat. Het tegenovergestelde van deze methode is isNotChecked ()
. is geselecteerd()
: retourneert een matcher die overeenkomt Uitzicht
s die zijn geselecteerd.Als u de test wilt uitvoeren, klikt u op de groene driehoek naast de methode of de klassenaam. Als u op de groene driehoek naast de klassenaam klikt, worden alle testmethoden in die klasse uitgevoerd, terwijl degene naast een methode de test alleen voor die methode uitvoert.
Hoera! Onze test is geslaagd!
Op een ViewInteraction
object dat wordt geretourneerd door te bellen OnView ()
, we kunnen acties simuleren die een gebruiker op een widget kan uitvoeren. We kunnen bijvoorbeeld een klikactie simuleren door simpelweg de Klik()
statische methode binnen de ViewActions
klasse. Dit zal a terugkeren ViewAction
object voor ons.
De documentatie zegt dat ViewAction
is:
Verantwoordelijk voor het uitvoeren van een interactie op het gegeven View-element.
@Test fun clickLoginButton_opensLoginUi () // ... onView (withId (R.id.btn_login)). Uitvoeren (klik ())
We voeren een klikgebeurtenis uit door eerst te callen uitvoeren()
. Deze methode voert de opgegeven actie (s) uit op de weergave geselecteerd door de huidige view matcher. Merk op dat we het een enkele actie of een lijst met acties kunnen doorgeven (in volgorde uitgevoerd). Hier hebben we het gegeven Klik()
. Andere mogelijke acties zijn:
typ Text()
om typetekst te imiteren in een Tekst bewerken
.duidelijke tekst()
om tekst te wissen in een Tekst bewerken
.Dubbelklik()
om te simuleren dubbelklikken a Uitzicht
.longClick ()
imiteren van lang klikken op een Uitzicht
.scrollTo ()
om scrollen te simuleren ScrollView
naar een bepaald Uitzicht
dat is zichtbaar. swipeLeft ()
om te simuleren van rechts naar links over het verticale midden van een Uitzicht
.Veel meer simulaties zijn te vinden binnen de ViewActions
klasse.
Laten we onze test afronden, om te valideren dat de LoginActivity
scherm wordt weergegeven wanneer de Log in knop is geklikt. Hoewel we al hebben gezien hoe te gebruiken controleren()
op een ViewInteraction
, laten we het opnieuw gebruiken, het doorgeven aan een ander ViewAssertion
.
@Test fun clickLoginButton_opensLoginUi () // ... onView (withId (R.id.tv_login)). Check (matches (isDisplayed ()))
Binnen in de LoginActivity
lay-outbestand, afgezien van Tekst bewerken
s en a Knop
, we hebben ook een Tekstweergave
met ID R.id.tv_login
. Dus doen we gewoon een controle om te bevestigen dat de Tekstweergave
is zichtbaar voor de gebruiker.
Nu kunt u de test opnieuw uitvoeren!
Uw tests moeten slagen als u alle stappen correct hebt uitgevoerd.
Dit is wat er gebeurde tijdens het uitvoeren van onze tests:
Hoofdactiviteit
de ... gebruiken activityRule
veld-.R.id.btn_login
) was zichtbaar (is weergegeven()
) voor de gebruiker.Klik()
) op die knop.LoginActivity
werd getoond aan de gebruiker door te controleren of a Tekstweergave
met id R.id.tv_login
in de LoginActivity
is zichtbaar.Je kunt altijd naar het Espresso-spiekbriefje gaan om de verschillende view-matchers te bekijken, acties te bekijken en beweringen te bekijken die beschikbaar zijn.
LoginActivity
SchermDit is onze LoginActivity.kt:
import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Button import android.widget.EditText import android.widget.TextView class LoginActivity: AppCompatActivity () private lateinit var gebruikersnaam Bewerkingstekst: EditText private lateinit var loginTitleTextView: TextView private lateinit var password EditText: EditText private lateinit var submitButton: Button override fun onCreate (savedInstanceState: Bundle?) super.onCreate (savedInstanceState) setContentView (R.layout.activity_login) gebruikersnaamEditText = findViewById (R.id.et_username) wachtwoordEditText = findViewById (R.id.et_password) submitButton = findViewById (R.id.btn_submit) loginTitleTextView = findViewById (R.id.tv_login) submitButton.setOnClickListener if (usernameEditText.text.toString () == "chike" && wachtwoordEditText. text.toString () == "password") loginTitleTextView.text = "Success" else loginTitleTextView.text = "Failure"
In de bovenstaande code, als de ingevoerde gebruikersnaam "chike" is en het wachtwoord "wachtwoord" is, dan is inloggen geslaagd. Voor elke andere invoer is het een fout. Laten we nu een espressotoets hiervoor schrijven!
Ga naar LoginActivity.kt, verplaats de cursor naar de LoginActivity
naam en druk op Shift-Control-T. kiezen Maak een nieuwe test ... in het pop-upmenu. Volg hetzelfde proces als voor ons MainActivity.kt, en klik op de OK knop.
import android.support.test.espresso.Espresso import android.support.test.espresso.Espresso.onView import android.support.test.espresso.action.ViewActions import android.support.test.espresso.assertion.ViewAssertions.matches import 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.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(LoginActivity :: class.java) private val gebruikersnaam = "chike" private val wachtwoord = "wachtwoord" @Test fun clickLoginButton_opensLoginUi () onView (withId (R.id.et_username)). Perform (ViewActions.typeText (gebruikersnaam)) onView (withId (R.id.et_password)). uitvoeren (ViewActions.typeText (wachtwoord)) onView (withId (R.id.btn_submit)). uitvoeren (ViewActions.scrollTo (), ViewActions.click ()) Espresso.onView (withId (R.id.tv_login)) .check (matches (withText ("Success")))
Deze testklasse lijkt erg op onze eerste. Als we de test uitvoeren, onze LoginActivity
scherm wordt geopend. De gebruikersnaam en het wachtwoord zijn ingetypt in de R.id.et_username
en R.id.et_password
velden respectievelijk. Vervolgens klikt Espresso op de voorleggen knop (R.id.btn_submit
). Het zal wachten tot a Uitzicht
met id R.id.tv_login
is te vinden met tekst lezen Succes.
RecyclerView
RecyclerViewActions
is de klasse die een reeks API's blootstelt om op a te werken RecyclerView
. RecyclerViewActions
maakt deel uit van een afzonderlijk artefact binnen de espresso-contrib
artefact, waaraan ook moet worden toegevoegd build.gradle:
androidTestImplementation 'com.android.support.test.espresso: espresso-contrib: 3.0.2'
Merk op dat dit artefact ook de API bevat voor gebruikersinterface die de navigatielade doortest DrawerActions
en DrawerMatchers
.
@RunWith (AndroidJUnit4 :: class) class MyListActivityTest // ... @Test fun clickItem () onView (withId (R.id.rv)) .perform (RecyclerViewActions .actionOnItemAtPosition(0, ViewActions.click ()))
Klik op een item op een willekeurige positie in een RecyclerView
, we roepen aan actionOnItemAtPosition ()
. We moeten het een soort item geven. In ons geval is het item het ViewHolder
klas binnen onze RandomAdapter
. Deze methode omvat ook twee parameters; de eerste is de positie, en de tweede is de actie (ViewActions.click ()
).
anders RecyclerViewActions
die kunnen worden uitgevoerd zijn:
actionOnHolderItem ()
: voert een ViewAction
op een weergave gekoppeld door viewHolderMatcher
. Dit stelt ons in staat om het te matchen met wat er in de ViewHolder
in plaats van de positie. scrollToPosition ()
: geeft a terug ViewAction
welke schuift RecyclerView
naar een positie.Vervolgens (zodra het scherm "Notitie toevoegen" geopend is), zullen we onze notitekst invoeren en de notitie opslaan. We hoeven niet te wachten tot het nieuwe scherm wordt geopend - Espresso zal dit automatisch voor ons doen. Het wacht tot een weergave met het ID R.id.add_note_title
kan gevonden worden.
Espresso maakt gebruik van een ander genoemd artefact espresso-intenties
voor het testen van intenties. Dit artefact is gewoon een uitbreiding op Espresso die zich richt op de validatie en spot van intenties. Laten we een voorbeeld bekijken.
Eerst moeten we de espresso-intenties
bibliotheek in ons project.
androidTestImplementation 'com.android.support.test.espresso: espresso-intents: 3.0.2'
import android.support.test.espresso.intent.rule.IntentsTestRule import android.support.test.runner.AndroidJUnit4 import org.junit.Rule import org.junit.runner.RunWith @RunWith (AndroidJUnit4 :: class) class PickContactActivityTest @ Regel @JvmField var intentRule = IntentsTestRule(PickContactActivity :: class.java)
IntentsTestRule
strekt ActivityTestRule
, dus ze hebben allebei hetzelfde gedrag. Dit is wat de doc zegt:
Deze klasse is een extensie vanActivityTestRule
, die Espresso-Intents initialiseert vóór elke geannoteerde testTest
en geeft Espresso-Intents vrij na elke testrun. De activiteit wordt na elke test beëindigd en deze regel kan op dezelfde manier worden gebruikt alsActivityTestRule
.
Het belangrijkste onderscheidende kenmerk is dat het extra functionaliteiten heeft om te testen startActivity ()
en startActivityForResult ()
met moppen en stompjes.
We gaan nu een scenario testen waarbij een gebruiker op een knop klikt (R.id.btn_select_contact
) op het scherm om een contact te kiezen uit de contactenlijst van de telefoon.
// ... @Test fun stubPick () var result = Instrumentation.ActivityResult (Activity.RESULT_OK, Intent (null, ContactsContract.Contacts.CONTENT_URI)) intending (hasAction (Intent.ACTION_PICK)) .responsWith (result) onView (withId ( R.id.btn_select_contact)). Uitvoeren (klik ()) bedoeld (allOf (toPackage ("com.google.android.contacts"), hasAction (Intent.ACTION_PICK), hasData (ContactsContract.Contacts.CONTENT_URI))) // ...
Hier gebruiken we plan ()
van de espresso-intenties
bibliotheek om een stub met een schijnreactie op te zetten voor onze ACTION_PICK
verzoek. Dit is wat er gebeurt PickContactActivity.kt wanneer de gebruiker op de knop met id klikt R.id.btn_select_contact
om een contact te kiezen.
fun pickContact (v: View) val i = Intent (Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) startActivityForResult (i, PICK_REQUEST)
plan ()
neemt in een Matcher
die overeenkomt met intenties waarvoor een reactie moet worden gegeven. Met andere woorden, de Matcher
geeft aan in welk verzoek je geïnteresseerd bent in stubbing. In ons eigen geval maken we gebruik van hasAction ()
(een hulpmethode in IntentMatchers
) om onze te vinden ACTION_PICK
verzoek. We roepen vervolgens aan antwoorden met()
, waarvoor het resultaat wordt ingesteld onActivityResult ()
. In ons geval heeft het resultaat Activity.RESULT_OK
, het simuleren van de gebruiker die een contact uit de lijst selecteert.
Vervolgens simuleren we door te klikken op de knop Selecteer contact, die oproept startActivityForResult ()
. Merk op dat onze stub de schijnreactie heeft verzonden naar onActivityResult ()
.
Ten slotte gebruiken we de beoogde ()
helper methode om eenvoudigweg te valideren dat de oproepen naar startActivity ()
en startActivityForResult ()
werden gemaakt met de juiste informatie.
In deze zelfstudie hebt u geleerd hoe u eenvoudig het Espresso-testraamwerk in uw Android Studio-project kunt gebruiken om uw testworkflow te automatiseren.
Ik raad ten zeerste aan om de officiële documentatie te bekijken voor meer informatie over het schrijven van UI-tests met Espresso.