Podstawy espresso

Z tego dokumentu dowiesz się, jak wykonywać typowe zadania związane z testowaniem automatycznym za pomocą Espresso API.

Interfejs Espresso API zachęca autorów testów do myślenia w kategoriach tego, co użytkownik może chcieć podczas interakcji z aplikacją – lokalizowania elementów interfejsu i interakcji z nimi. Jednocześnie platforma uniemożliwia bezpośredni dostęp do aktywności i widoków aplikacji, ponieważ trzymanie się tych obiektów są głównym źródłem niestabilności testów. Z tego powodu nie widzisz metod takich jak getView() i getCurrentActivity() w interfejsie Espresso API. Nadal możesz bezpiecznie wykonywać działania na wyświetleniach, implementując własne podklasy ViewAction i ViewAssertion.

Komponenty API

Jej główne składniki to:

  • Espresso – punkt wejścia do interakcji z widokami (za pomocą onView() i onData()). Ujawnia też interfejsy API, które nie muszą być powiązane z żadnym widokiem, jako pressBack().
  • ViewMatchers – zbiór obiektów, które stosują interfejsu Matcher<? super View>. Możesz przekazać jeden lub więcej z nich do Metoda onView() do zlokalizowania widoku w bieżącej hierarchii.
  • ViewAction – zbiór ViewAction obiektów, które można przekazać do metodę ViewInteraction.perform(), np. click().
  • ViewAssertions – zbiór ViewAssertion obiektów, które można zaliczono metodę ViewInteraction.check(). Najczęściej używa się funkcji pasuje do asercji, w którym przy użyciu dopasowania widoku obecnie wybranego widoku.

Przykład:

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()));

Znajdowanie widoku

W większości przypadków metoda onView() wykorzystuje dopasowanie hamcrest który ma pasować do jednego – i tylko jednego – widoku w bieżącym widoku w hierarchii. Dopasowania mają duże możliwości i będą znane użytkownikom, którzy z nich korzystali używając Mockito lub JUnit. Jeśli nie znacie funkcji dopasowywania hamcrest, warto najpierw przyjrzeć się temu, prezentację.

Często żądany widok ma unikalny element R.id, a proste dopasowanie withId doprecyzować wyszukiwanie. Istnieje jednak wiele uzasadnionych przypadków, nie można określić funkcji R.id w czasie tworzenia testu. Na przykład: konkretny widok nie może mieć parametru R.id lub R.id nie jest unikalny. Może to spowodować testy z instrumentacją są nieostre i skomplikowane w pisaniu, ponieważ nie działa w celu uzyskania dostępu do widoku danych za pomocą funkcji findViewById(). Dzięki temu możesz muszą uzyskać dostęp do prywatnych członków aktywności lub fragmentu z widokiem, znajdź kontener ze znanym identyfikatorem R.id i przejdź do jego zawartości dla z danego widoku.

Espresso dobrze radzi sobie z tym problemem, umożliwiając zawężenie obrazu przy użyciu istniejących obiektów ViewMatcher lub własnych obiektów niestandardowych.

Znalezienie widoku według wartości R.id sprowadza się do wywołania strony onView():

Kotlin

onView(withId(R.id.my_view))

Java

onView(withId(R.id.my_view));

Czasami wartości R.id są czasami wspólne dla kilku widoków. W takiej sytuacji próba użycia określonego elementu R.id spowoduje wyjątek, taki jak AmbiguousViewMatcherException Komunikat o wyjątku zawiera reprezentacja bieżącej hierarchii widoków, którą możesz przeszukiwać widoki danych pasujące do nieunikalnych widoków danych R.id:

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****

Analizując różne atrybuty widoków, możesz zauważyć, właściwości możliwych do zidentyfikowania. W powyższym przykładzie jeden z widoków zawiera tekst "Hello!" Pozwala zawęzić wyszukiwanie za pomocą kombinacji dopasowania:

Kotlin

onView(allOf(withId(R.id.my_view), withText("Hello!")))

Java

onView(allOf(withId(R.id.my_view), withText("Hello!")));

Możesz też nie zmieniać żadnych dopasowań:

Kotlin

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))

Java

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));

Zobacz ViewMatchers dla dopasowania widoku danych dostarczonego przez Espresso.

co należy wziąć pod uwagę

  • W dobrze funkcjonującej aplikacji wszystkie widoki, z których użytkownik może wchodzić w interakcje powinien zawierać tekst opisowy lub opis treści. Zobacz Ułatwianie dostępu do aplikacji . Jeśli nie możesz zawęzić wyszukiwania za pomocą funkcji withText() lub withContentDescription(), potraktuj to jako błąd związany z ułatwieniami dostępu.
  • Używaj funkcji dopasowania opisowego, która znajduje ten widok . Nie określaj za bardzo, bo zmusza to platformę do działania niż jest konieczne. Jeśli na przykład widok jest jednoznacznie identyfikowany na podstawie tekstu, nie muszą określać, że widok można też przypisać z elementu TextView. Przez długi czas R.id widoku powinno wystarczyć.
  • Jeśli widok docelowy znajduje się w elemencie AdapterView, np. ListView, GridView lub Spinner – metoda onView() może nie działać. W tych przypadków, użyj onData().

Wykonywanie działania na widoku danych

Po znalezieniu dopasowania odpowiedniego do widoku docelowego możesz: wykonać na nim instancje ViewAction za pomocą metody „performance”.

Aby na przykład kliknąć widok:

Kotlin

onView(...).perform(click())

Java

onView(...).perform(click());

Z jednym wywołaniem możesz wykonać więcej niż jedno działanie:

Kotlin

onView(...).perform(typeText("Hello"), click())

Java

onView(...).perform(typeText("Hello"), click());

Jeśli widok, nad którym pracujesz, znajduje się w obszarze ScrollView (pionowym lub w poziomie), rozważ wcześniejsze działania, które wymagają wyświetlane, takie jak click() i typeText(), z funkcją scrollTo(). Ten upewnia się, że widok został wyświetlony przed wykonaniem innej czynności:

Kotlin

onView(...).perform(scrollTo(), click())

Java

onView(...).perform(scrollTo(), click());

Zobacz ViewActions dla działań wyświetlania zapewnianych przez Espresso.

Sprawdź asercje wyświetleń

Asercje można stosować do obecnie wybranego widoku za pomocą interfejsu check() . Najczęściej używaną asercją jest matches(). Wykorzystuje ViewMatcher obiekt, który potwierdza stan aktualnie wybranego widoku.

Aby na przykład sprawdzić, czy widok danych zawiera tekst "Hello!":

Kotlin

onView(...).check(matches(withText("Hello!")))

Java

onView(...).check(matches(withText("Hello!")));

Jeśli chcesz twierdzić, że "Hello!" jest treścią widoku, nie należy stosować się do takich praktyk:

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()));

Z drugiej strony, jeśli chcesz potwierdzić, że widok z tekstem "Hello!" jest – na przykład po zmianie flagi widoczności – jest w porządku.

Wyświetl prosty test asercji

W tym przykładzie SimpleActivity zawiera elementy Button i TextView. Gdy kliknięcie przycisku spowoduje, że zawartość elementu TextView zmieni się w "Hello Espresso!".

Aby to zrobić za pomocą Espresso:

Kliknij przycisk

Najpierw znajdź właściwość, która pomoże Ci go znaleźć. przycisk w tabeli SimpleActivity ma zgodnie z oczekiwaniami unikalny atrybut R.id.

Kotlin

onView(withId(R.id.button_simple))

Java

onView(withId(R.id.button_simple));

Teraz wykonaj następujące czynności:

Kotlin

onView(withId(R.id.button_simple)).perform(click())

Java

onView(withId(R.id.button_simple)).perform(click());

Sprawdzanie tekstu TextView

Element TextView z tekstem do zweryfikowania ma też unikalny identyfikator R.id:

Kotlin

onView(withId(R.id.text_simple))

Java

onView(withId(R.id.text_simple));

Teraz aby zweryfikować tekst treści:

Kotlin

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))

Java

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));

Sprawdź wczytywanie danych w widokach adaptera

AdapterView to specjalny typ widżetu, który dynamicznie wczytuje dane z: przejściówkę. Najczęstszym przykładem wartości AdapterView jest ListView. Jako w przeciwieństwie do statycznych widżetów, takich jak LinearLayout, Liczba elementów podrzędnych, które można wczytać do bieżącej hierarchii widoków: AdapterView. Prosta Wyszukiwanie za pomocą funkcji onView() nie może znaleźć widoków, które nie zostały wczytane.

Espresso obsługuje to, tworząc osobny punkt wejścia onData(), który wczytanie odpowiedniego elementu adaptera i jego skupienie przed ani jej elementów podrzędnych.

Ostrzeżenie: niestandardowe implementacje AdapterView może mieć problemy z: onData() jeśli łamią umowy dziedziczenia, zwłaszcza getItem() API. W takim przypadku najlepszym sposobem jest refaktoryzacji kodu aplikacji. Jeśli nie możesz tego zrobić, możesz zaimplementować tag pasujące niestandardowe: AdapterViewProtocol. Aby dowiedzieć się więcej, wykonaj spójrz na domyślne AdapterViewProtocols zajęcia prowadzone przez Espresso.

Prosty test widoku adaptera

Ten prosty test pokazuje, jak korzystać z narzędzia onData(). SimpleActivity zawiera Spinner z kilkoma elementami przedstawiającymi rodzaje napojów kawowych. Gdy jest wybrany element, wartość TextView zmienia się na "One %s a day!", przy czym %s reprezentuje wybrany element.

Celem tego testu jest otwarcie Spinner, wybranie konkretnego elementu i sprawdź, czy TextView zawiera ten element. Ponieważ klasa Spinner jest oparta na danych w domenie AdapterView, zalecamy użycie onData() zamiast onView() dla pasującego do elementu.

Otwórz wybór elementu

Kotlin

onView(withId(R.id.spinner_simple)).perform(click())

Java

onView(withId(R.id.spinner_simple)).perform(click());

Zaznaczenie elementu

Spinner tworzy ListView z zawartością wyboru elementu. Taki widok może być bardzo długi, a element może nie mieć w nim wkładu. w hierarchii. Używając metody onData(), wymuszamy wyświetlenie wybranego elementu w widoku w hierarchii. Elementy w tabeli Spinner są ciągami tekstowymi, więc chcemy dopasować je do elementu. które jest równe ciągowi "Americano":

Kotlin

onData(allOf(`is`(instanceOf(String::class.java)),
        `is`("Americano"))).perform(click())

Java

onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());

Sprawdź, czy tekst jest prawidłowy

Kotlin

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))))

Java

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))));

Debugowanie

Jeśli test nie powiedzie się, Espresso udostępnia przydatne informacje na potrzeby debugowania:

Logowanie

Espresso zapisuje w logcat wszystkie działania związane z wyświetlaniem. Na przykład:

ViewInteraction: Performing 'single click' action on view with text: Espresso

Wyświetl hierarchię

Espresso drukuje hierarchię widoku w wiadomości wyjątku, gdy onView() niepowodzenie.

  • Jeśli onView() nie znajdzie widoku docelowego, NoMatchingViewException to rzucona. Aby przeanalizować hierarchię widoków, możesz sprawdzić hierarchię widoków w ciągu wyjątków dlaczego dopasowanie nie dało żadnych wyświetleń.
  • Jeśli onView() znajdzie wiele widoków danych pasujących do danego dopasowania, AmbiguousViewMatcherException otrzymuje rzut. Hierarchia widoków jest drukowana, a wszystkie dopasowane widoki są oznaczone etykietą MATCHES:
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****

Ze złożoną hierarchią widoków lub nieoczekiwanym działaniem widżetów zawsze warto użyć Wyświetlający hierarchię w Android Studio dla wyjaśnienie.

Ostrzeżenia dotyczące widoku karty

Espresso ostrzega użytkowników o obecności AdapterView widżetów. Gdy onView() operacja zwraca widżety NoMatchingViewException i AdapterView są dostępnych w hierarchii widoków, najczęściej używanym rozwiązaniem jest użycie elementu onData(). Wiadomość o wyjątku będzie zawierać ostrzeżenie z listą widoków adaptera. Możesz użyć tych informacji do wywołania onData() w celu wczytania widoku docelowego.

Dodatkowe materiały

Więcej informacji o używaniu Espresso w testach na Androidzie znajdziesz w poniższe zasoby.

Próbki