Espressogrundlagen

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() und onData()). 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 Methode onView() übergeben, um eine Ansicht in der aktuellen Ansichtshierarchie zu suchen.
  • ViewActions: Eine Sammlung von ViewAction-Objekten, die an die Methode ViewInteraction.perform() übergeben werden können, z. B. click().
  • ViewAssertions: Eine Sammlung von ViewAssertion-Objekten, die die Methode ViewInteraction.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() oder withContentDescription() 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 der R.id der Ansicht ausreichen.
  • Wenn sich die Zielansicht in einem AdapterView befindet, z. B. ListView, GridView oder Spinner, funktioniert die Methode onView() möglicherweise nicht. In diesen Fällen sollten Sie stattdessen onData() 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 ein NoMatchingViewException 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 ein AmbiguousViewMatcherException ausgelöst. Die Ansichtshierarchie wird gedruckt und alle übereinstimmenden Ansichten sind mit dem Label MATCHES 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