In diesem Dokument wird erläutert, wie Sie häufige automatisierte Testaufgaben mit der Espresso API ausführen.
Die Espresso API ermutigt Testautoren dazu, sich Gedanken darüber zu machen, was ein Nutzer während der Interaktion mit der Anwendung tun könnte: UI-Elemente suchen und mit ihnen interagieren. Gleichzeitig verhindert das Framework den direkten Zugriff auf Aktivitäten und Ansichten der Anwendung, da das Zurückhalten dieser Objekte und das Bearbeiten außerhalb des UI-Threads eine Hauptursache für unzuverlässige Tests sind. Daher werden in der Espresso API keine Methoden wie getView()
und getCurrentActivity()
angezeigt.
Sie können weiterhin sicher mit Ansichten arbeiten, wenn Sie Ihre eigenen abgeleiteten Klassen von ViewAction
und ViewAssertion
implementieren.
API-Komponenten
Espresso besteht hauptsächlich aus folgenden Elementen:
- Espresso – Einstiegspunkt für Interaktionen mit Ansichten (über
onView()
undonData()
). Stellt auch APIs bereit, die nicht unbedingt an eine Ansicht gebunden sind, z. B.pressBack()
. - ViewMatchers: Eine Sammlung von Objekten, die die
Matcher<? super View>
-Schnittstelle implementieren. Sie können eine oder mehrere dieser Werte an die MethodeonView()
übergeben, um eine Ansicht in der aktuellen Ansichtshierarchie zu suchen. - ViewActions: Eine Sammlung von
ViewAction
-Objekten, die an die MethodeViewInteraction.perform()
übergeben werden können, z. B.click()
. - ViewAssertions: Eine Sammlung von
ViewAssertion
-Objekten, die die MethodeViewInteraction.check()
übergeben kann. Meistens werden Sie die Übereinstimmungs-Assertion verwenden, die einen Ansichts-Matcher verwendet, um den Status der aktuell ausgewählten Ansicht zu bestätigen.
Beispiel:
Kotlin
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()))
Java
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()));
Ansicht suchen
In den meisten Fällen verwendet die Methode onView()
einen Hamcrest-Matcher, von dem erwartet wird, dass er einer – und nur einer – Ansicht innerhalb der aktuellen Hierarchie entspricht. Matcher sind leistungsstark und sind denjenigen vertraut, die sie mit Mockito oder JUnit verwendet haben. Wenn Sie mit Hamcrest-Matchern nicht vertraut sind, empfehlen wir Ihnen, sich diese Präsentation kurz anzusehen.
Oft hat die gewünschte Ansicht eine eindeutige R.id
und ein einfacher withId
-Matcher schränkt die Ansichtssuche ein. Es gibt jedoch viele legitime Fälle, in denen Sie R.id
bei der Testentwicklung nicht bestimmen können. Es kann beispielsweise sein, dass die jeweilige Ansicht kein R.id
hat oder die R.id
nicht eindeutig ist. Dies kann das Schreiben von normalen Instrumentierungstests beeinträchtigen und kompliziert machen, da die normale Methode für den Zugriff auf die Ansicht – mit findViewById()
– nicht funktioniert. Daher müssen Sie möglicherweise auf private Mitglieder der Aktivität oder des Fragments zugreifen, die die Ansicht enthalten, oder einen Container mit einer bekannten R.id
suchen und zu dessen Inhalt für die jeweilige Ansicht wechseln.
Espresso löst dieses Problem sauber, indem Sie die Ansicht mithilfe von vorhandenen ViewMatcher
-Objekten oder Ihren eigenen benutzerdefinierten Objekten eingrenzen können.
Um eine Ansicht anhand des zugehörigen R.id
zu finden, müssen Sie nur onView()
aufrufen:
Kotlin
onView(withId(R.id.my_view))
Java
onView(withId(R.id.my_view));
Manchmal werden R.id
-Werte von mehreren Datenansichten gemeinsam verwendet. In diesem Fall wird durch den Versuch, ein bestimmtes R.id
-Objekt zu verwenden, eine Ausnahme wie AmbiguousViewMatcherException
ausgegeben. Die Ausnahmemeldung liefert Ihnen eine Textdarstellung der aktuellen Ansichtshierarchie. Sie können nach Ansichten suchen, die dem nicht eindeutigen R.id
entsprechen:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
Bei der Analyse der verschiedenen Attribute der Datenansichten finden Sie möglicherweise eindeutig identifizierbare Properties. Im obigen Beispiel enthält eine der Ansichten den Text "Hello!"
. So können Sie Ihre Suche mithilfe von Kombinations-Matchern eingrenzen:
Kotlin
onView(allOf(withId(R.id.my_view), withText("Hello!")))
Java
onView(allOf(withId(R.id.my_view), withText("Hello!")));
Sie können auch festlegen, dass keine der Abgleichausdrücke rückgängig gemacht werden sollen:
Kotlin
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
Java
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));
Unter ViewMatchers
finden Sie die von Espresso bereitgestellten Ansicht-Matcher.
Wissenswertes
- In einer Anwendung, die gut funktioniert, sollten alle Ansichten, mit denen ein Nutzer interagieren kann, entweder beschreibenden Text oder eine Inhaltsbeschreibung enthalten. Weitere Informationen finden Sie unter Apps barrierefrei gestalten. Wenn Sie eine Suche nicht mit
withText()
oderwithContentDescription()
eingrenzen können, sollten Sie dies als Fehler bei der Barrierefreiheit betrachten. - Verwenden Sie das am wenigsten beschreibende Tool, mit dem die gewünschte Datenansicht gefunden wird. Geben Sie nicht zu viel an, da das Framework dadurch gezwungen wird, mehr Arbeit zu erledigen als nötig. Wenn eine Ansicht beispielsweise durch ihren Text eindeutig identifizierbar ist, müssen Sie nicht angeben, dass die Ansicht auch aus
TextView
zuweisbar ist. Für viele Ansichten sollte derR.id
der Ansicht ausreichen. - Wenn sich die Zielansicht in einem
AdapterView
befindet, z. B.ListView
,GridView
oderSpinner
, funktioniert die MethodeonView()
möglicherweise nicht. In diesen Fällen sollten Sie stattdessenonData()
verwenden.
Aktion für eine Datenansicht ausführen
Wenn Sie einen geeigneten Abgleich für die Zielansicht gefunden haben, können Sie mithilfe der Methode „Perform“ Instanzen von ViewAction
ausführen.
So klicken Sie beispielsweise auf die Ansicht:
Kotlin
onView(...).perform(click())
Java
onView(...).perform(click());
Sie können mit einem „Perform“-Aufruf mehrere Aktionen ausführen:
Kotlin
onView(...).perform(typeText("Hello"), click())
Java
onView(...).perform(typeText("Hello"), click());
Wenn sich die Ansicht, mit der Sie arbeiten, in einer ScrollView
(vertikal oder horizontal) befindet, sollten Sie vorherige Aktionen, die die Darstellung der Ansicht erfordern (z. B. click()
und typeText()
), mit scrollTo()
in Betracht ziehen. Dadurch wird sichergestellt, dass die Ansicht angezeigt wird, bevor mit der anderen Aktion fortgefahren wird:
Kotlin
onView(...).perform(scrollTo(), click())
Java
onView(...).perform(scrollTo(), click());
Die von Espresso bereitgestellten Anzeigeaktionen finden Sie unter ViewActions
.
Assertions prüfen
Mit der Methode check()
können Assertions auf die aktuell ausgewählte Ansicht angewendet werden. Die am häufigsten verwendete Assertion ist die Assertion matches()
. Sie verwendet ein ViewMatcher
-Objekt, um den Status der aktuell ausgewählten Ansicht zu bestätigen.
So prüfen Sie beispielsweise, ob eine Ansicht den Text "Hello!"
enthält:
Kotlin
onView(...).check(matches(withText("Hello!")))
Java
onView(...).check(matches(withText("Hello!")));
Wenn Sie behaupten möchten, dass "Hello!"
Inhalt der Ansicht ist, gilt Folgendes:
Kotlin
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
Java
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
Wenn Sie dagegen bestätigen möchten, dass eine Ansicht mit dem Text "Hello!"
sichtbar ist – z. B. nach einer Änderung des Flags für die Sichtbarkeit der Ansichten –, ist der Code in Ordnung.
Einfacher Assertion-Test ansehen
In diesem Beispiel enthält SimpleActivity
einen Button
und einen TextView
. Wenn auf die Schaltfläche geklickt wird, ändert sich der Inhalt von TextView
in "Hello Espresso!"
.
So kannst du das mit Espresso testen:
Klicken Sie auf die Schaltfläche
Der erste Schritt besteht darin, nach einer Eigenschaft zu suchen, mit der sich die Schaltfläche leichter finden lässt. Die Schaltfläche in SimpleActivity
hat wie erwartet einen eindeutigen R.id
.
Kotlin
onView(withId(R.id.button_simple))
Java
onView(withId(R.id.button_simple));
Führen Sie nun den Klick aus:
Kotlin
onView(withId(R.id.button_simple)).perform(click())
Java
onView(withId(R.id.button_simple)).perform(click());
TextView-Text prüfen
Das TextView
mit dem zu bestätigenden Text hat ebenfalls eine eindeutige R.id
:
Kotlin
onView(withId(R.id.text_simple))
Java
onView(withId(R.id.text_simple));
So überprüfen Sie den Inhaltstext:
Kotlin
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
Java
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
Laden von Daten in Adapteransichten prüfen
AdapterView
ist ein spezieller Widgettyp, dessen Daten dynamisch von einem Adapter geladen werden. Das gängigste Beispiel für eine AdapterView
ist ListView
. Im Gegensatz zu statischen Widgets wie LinearLayout
kann nur ein Teil der untergeordneten AdapterView
-Elemente in die aktuelle Ansichtshierarchie geladen werden. Mit einer einfachen onView()
-Suche werden keine Ansichten gefunden, die derzeit nicht geladen sind.
In Espresso wird dafür ein separater onData()
-Einstiegspunkt bereitgestellt, über den zuerst das betreffende Adapterelement geladen und vor der Ausführung auf dem Gerät oder einem seiner untergeordneten Elemente in den Fokus rückt.
Warnung:Bei benutzerdefinierten Implementierungen von AdapterView
können Probleme mit der Methode onData()
auftreten, wenn sie gegen Übernahmeverträge verstoßen, insbesondere die getItem()
API. In solchen Fällen ist es am besten, den Anwendungscode zu refaktorieren. Wenn dies nicht möglich ist, können Sie eine übereinstimmende benutzerdefinierte AdapterViewProtocol
implementieren. Weitere Informationen finden Sie in der Standardklasse
AdapterViewProtocols
von Espresso.
Adapteransicht – einfacher Test
Dieser einfache Test zeigt, wie onData()
verwendet wird. SimpleActivity
enthält einen Spinner
mit einigen Elementen, die Arten von Kaffeegetränken darstellen. Wenn ein Element ausgewählt wird, ändert sich TextView
in "One %s a day!"
, wobei %s
für das ausgewählte Element steht.
Ziel dieses Tests ist es, das Spinner
zu öffnen, ein bestimmtes Element auszuwählen und zu prüfen, ob das TextView
das Element enthält. Da die Klasse Spinner
auf AdapterView
basiert, wird empfohlen, onData()
anstelle von onView()
zu verwenden, um ein Element abzugleichen.
Elementauswahl öffnen
Kotlin
onView(withId(R.id.spinner_simple)).perform(click())
Java
onView(withId(R.id.spinner_simple)).perform(click());
Element auswählen
Für die Elementauswahl erstellt Spinner
ein ListView
mit seinem Inhalt.
Diese Ansicht kann sehr lang sein und das Element wird möglicherweise nicht zur Hierarchie der Ansicht beigetragen. Mit onData()
erzwingen wir das gewünschte Element in der Ansichtshierarchie. Die Elemente in Spinner
sind Strings. Deshalb soll ein Element ermittelt werden, das mit dem String "Americano"
übereinstimmt:
Kotlin
onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))).perform(click())
Java
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
Überprüfe, ob der Text korrekt ist.
Kotlin
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))))
Java
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
Debugging
Espresso bietet nützliche Informationen zur Fehlerbehebung, wenn ein Test fehlschlägt:
Protokollierung
Espresso protokolliert alle Anzeigeaktionen in Logcat. Beispiele:
ViewInteraction: Performing 'single click' action on view with text: Espresso
Hierarchie ansehen
Wenn onView()
fehlschlägt, gibt Espresso die Ansichtshierarchie in der Ausnahmemeldung aus.
- Wenn
onView()
die Zielansicht nicht findet, wird einNoMatchingViewException
ausgelöst. Sie können die Ansichtshierarchie im Ausnahmestring untersuchen, um zu ermitteln, warum der Matcher keine übereinstimmenden Ansichten gefunden hat. - Wenn
onView()
mehrere Ansichten findet, die mit dem angegebenen Matcher übereinstimmen, wird einAmbiguousViewMatcherException
ausgelöst. Die Ansichtshierarchie wird gedruckt und alle übereinstimmenden Ansichten sind mit dem LabelMATCHES
gekennzeichnet:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
Bei einer komplizierten Ansichtshierarchie oder einem unerwarteten Verhalten von Widgets ist es immer hilfreich, den Hierarchy Viewer in Android Studio zur Erklärung zu verwenden.
Warnungen zur Adapteransicht
Espresso warnt Nutzer vor dem Vorhandensein von AdapterView
-Widgets. Wenn ein onView()
-Vorgang ein NoMatchingViewException
ausgibt und AdapterView
-Widgets in der Ansichtshierarchie vorhanden sind, ist die gängigste Lösung, onData()
zu verwenden.
Die Ausnahmemeldung enthält eine Warnung mit einer Liste der Adapteransichten.
Sie können diese Informationen verwenden, um onData()
aufzurufen und die Zielansicht zu laden.
Weitere Informationen
Weitere Informationen zur Verwendung von Espresso in Android-Tests finden Sie in den folgenden Ressourcen.
Produktproben
- CustomMatcherSample: Zeigt, wie Espresso erweitert wird, um die Hinweiseigenschaft eines
EditText
-Objekts abzugleichen. - RecyclerViewSample:
RecyclerView
Aktionen für Espresso. - (Mehr...)