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()
ionData()
). Ujawnia też interfejsy API, które nie muszą być powiązane z żadnym widokiem, jakopressBack()
. - ViewMatchers – zbiór obiektów, które stosują
interfejsu
Matcher<? super View>
. Możesz przekazać jeden lub więcej z nich do MetodaonView()
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()
lubwithContentDescription()
, 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 czasR.id
widoku powinno wystarczyć. - Jeśli widok docelowy znajduje się w elemencie
AdapterView
, np.ListView
,GridView
lubSpinner
– metodaonView()
może nie działać. W tych przypadków, użyjonData()
.
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
- CustomMatcherSample:
Pokazuje, jak rozszerzyć część Espresso, aby pasowała do właściwości podpowiedzi obiektu
EditText
. - RecyclerViewSample:
Działania:
RecyclerView
dla Espresso. - (więcej...)