Android
Testowanie interfejsu użytkownika za pomocą espresso
Szukaj…
Uwagi
Espresso
Ściągawka do espresso pomoże Ci napisać testy i to, co chcesz przetestować:
https://google.github.io/android-testing-support-library/docs/espresso/cheatsheet/
Również zawsze dobrym miejscem odniesienia jest oficjalna dokumentacja:
https://google.github.io/android-testing-support-library/docs/espresso/index.html
Zaawansowane propozycje filmów espresso od Google: https://www.youtube.com/watch?v=isihPOY2vS4
Rozwiązywanie problemów
- Podczas próby przewijania pamiętaj, aby najpierw zamknąć klawiaturę:
Uwaga: niestosowanie wersji „Espresso” nic nie zrobi, jeśli zostanie użyte poza ViewAction. Może to nie być oczywiste, jeśli masz import w wersji ViewAction, ponieważ mają one dokładnie taką samą nazwę metody.
ViewActions.closeSoftKeyboard;
Espresso.closeSoftKeyboard();
- Podczas uruchamiania testów razem w pakiecie, a nie pojedynczo, należy pamiętać, że działanie z poprzedniego testu może być nadal uruchomione. Nie polegaj na wywołaniu onDestroy () poprzedniego testu przed bieżącymi testami onResume (). Okazuje się, że to w rzeczywistości błąd : http://b.android.com/201513
Skonfiguruj espresso
W pliku build.gradle
modułu aplikacji na Androida dodaj kolejne zależności:
dependencies {
// Android JUnit Runner
androidTestCompile 'com.android.support.test:runner:0.5'
// JUnit4 Rules
androidTestCompile 'com.android.support.test:rules:0.5'
// Espresso core
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2.2'
//UI Automator tests
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.2.2'
}
Podaj parametr AndroidJUnitRunner
dla parametru testInstrumentationRunner
w pliku build.gradle
.
android {
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
Dodatkowo dodaj tę zależność, aby zapewnić zamierzoną obsługę drwiny
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
I dodaj ten do wsparcia testowania webview
// Espresso-web for WebView support
androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'
Utwórz klasę testową espresso
Umieść następną klasę Java w src / androidTest / java i uruchom ją.
public class UITest {
@Test public void Simple_Test() {
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
}
}
Otwórz Zamknij szufladę
public final class DrawerLayoutTest {
@Test public void Open_Close_Drawer_Layout() {
onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer());
onView(withId(R.id.drawer_layout)).perform(actionCloseDrawer());
}
public static ViewAction actionOpenDrawer() {
return new ViewAction() {
@Override public Matcher<View> getConstraints() {
return isAssignableFrom(DrawerLayout.class);
}
@Override public String getDescription() {
return "open drawer";
}
@Override public void perform(UiController uiController, View view) {
((DrawerLayout) view).openDrawer(GravityCompat.START);
}
};
}
public static ViewAction actionCloseDrawer() {
return new ViewAction() {
@Override public Matcher<View> getConstraints() {
return isAssignableFrom(DrawerLayout.class);
}
@Override public String getDescription() {
return "close drawer";
}
@Override public void perform(UiController uiController, View view) {
((DrawerLayout) view).closeDrawer(GravityCompat.START);
}
};
}
}
Prosty test interfejsu użytkownika espresso
Narzędzia do testowania interfejsu użytkownika
Dwa główne narzędzia, które są obecnie najczęściej używane do testowania interfejsu użytkownika, to Appium i Espresso.
Appium | Espresso |
---|---|
test blackbox | testowanie białych / szarych skrzynek |
to, co widzisz, możesz przetestować | potrafi zmienić wewnętrzne działanie aplikacji i przygotować ją do testowania, np. zapisać niektóre dane w bazie danych lub wspólne preferencje przed uruchomieniem testu |
używane głównie w testach integracyjnych od końca do końca i przepływach użytkowników | testowanie funkcjonalności ekranu i / lub przepływu |
można wyodrębnić, aby napisany test można było wykonać na iOS i Androidzie | Tylko Android |
dobrze wspierany | dobrze wspierany |
obsługuje równoległe testowanie na wielu urządzeniach z siatką selenową | Nie jest gotowe do testowania równoległego, wtyczki takie jak Spoon istnieją, dopóki nie pojawi się prawdziwa obsługa Google |
Jak dodać espresso do projektu
dependencies {
// Set this dependency so you can use Android JUnit Runner
androidTestCompile 'com.android.support.test:runner:0.5'
// Set this dependency to use JUnit 4 rules
androidTestCompile 'com.android.support.test:rules:0.5'
// Set this dependency to build and run Espresso tests
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
// Set this dependency to build and run UI Automator tests
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.2.2'
}
UWAGA Jeśli używasz najnowszych bibliotek wsparcia, adnotacji itp., Musisz wykluczyć starsze wersje z espresso, aby uniknąć kolizji:
// there is a conflict with the test support library (see http://stackoverflow.com/questions/29857695)
// so for now re exclude the support-annotations dependency from here to avoid clashes
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
// exclude a couple of more modules here because of <http://stackoverflow.com/questions/29216327> and
// more specifically of <https://code.google.com/p/android-test-kit/issues/detail?id=139>
// otherwise you'll receive weird crashes on devices and dex exceptions on emulators
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude group: 'com.android.support', module: 'design'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
//excluded specific packages due to https://code.google.com/p/android/issues/detail?id=183454
androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
androidTestCompile('com.android.support.test.espresso:espresso-web:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
androidTestCompile('com.android.support.test:runner:0.5') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
androidTestCompile('com.android.support.test:rules:0.5') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
Oprócz tych importów konieczne jest dodanie narzędzia testującego Androida do build.gradle android.defaultConfig:
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
Konfiguracja urządzenia
W przypadku testu niestabilnego zaleca się ustawienie następujących ustawień na urządzeniach:
- Opcje programistyczne / Wyłącz animacje - zmniejsza kruchość testów
- Opcje programistyczne / Czuwaj - jeśli masz dedykowane urządzenia do testów, jest to przydatne
- Opcje programistyczne / Rozmiary buforów rejestratora - ustaw na wyższą liczbę, jeśli uruchomisz na telefonie bardzo duże pakiety testowe
- Dostępność / Opóźnienie dotyku i przytrzymania - długie, aby uniknąć problemów z pukaniem w espresso
Całkiem konfiguracja z prawdziwego świata ha? Cóż, teraz, gdy to nie przeszkadza, zobaczmy, jak skonfigurować mały test
Pisanie testu
Załóżmy, że mamy następujący ekran: Ekran zawiera:
- pole wprowadzania tekstu - R.id.textEntry
- przycisk wyświetlający pasek kliknięcia z tekstem wpisanym po kliknięciu - R.id.shownSnackbarBtn
- pasek przekąska, który powinien zawierać tekst wpisany przez użytkownika - android.support.design.R.id.snackbar_text
Teraz stwórzmy klasę, która przetestuje nasz przepływ:
/**
* Testing of the snackbar activity.
**/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class SnackbarActivityTest{
//espresso rule which tells which activity to start
@Rule
public final ActivityTestRule<SnackbarActivity> mActivityRule =
new ActivityTestRule<>(SnackbarActivity.class, true, false);
@Override
public void tearDown() throws Exception {
super.tearDown();
//just an example how tear down should cleanup after itself
mDatabase.clear();
mSharedPrefs.clear();
}
@Override
public void setUp() throws Exception {
super.setUp();
//setting up your application, for example if you need to have a user in shared
//preferences to stay logged in you can do that for all tests in your setup
User mUser = new User();
mUser.setToken("randomToken");
}
/**
*Test methods should always start with "testXYZ" and it is a good idea to
*name them after the intent what you want to test
**/
@Test
public void testSnackbarIsShown() {
//start our activity
mActivityRule.launchActivity(null);
//check is our text entry displayed and enter some text to it
String textToType="new snackbar text";
onView(withId(R.id.textEntry)).check(matches(isDisplayed()));
onView(withId(R.id.textEntry)).perform(typeText(textToType));
//click the button to show the snackbar
onView(withId(R.id.shownSnackbarBtn)).perform(click());
//assert that a view with snackbar_id with text which we typed and is displayed
onView(allOf(withId(android.support.design.R.id.snackbar_text),
withText(textToType))) .check(matches(isDisplayed()));
}
}
Jak zauważyłeś, pojawiają się 3-4 rzeczy, które możesz zauważyć często:
onView (withXYZ) <- viewMatchers Za ich pomocą można znaleźć elementy na ekranie
perform (click ()) <- viewActions, możesz wykonywać akcje na elementach, które wcześniej znalazłeś
check (mecze (isDisplayed ())) <- viewAssertions, sprawdza, co chcesz zrobić na ekranach wcześniej znalezionych
Wszystkie te i wiele innych można znaleźć tutaj: https://google.github.io/android-testing-support-library/docs/espresso/cheatsheet/index.html
To wszystko, teraz możesz uruchomić test, klikając prawym przyciskiem myszy nazwę klasy / test i wybierając Uruchom test lub za pomocą polecenia:
./gradlew connectedFLAVORNAMEAndroidTest
Nawigacja w górę
@Test
public void testUpNavigation() {
intending(hasComponent(ParentActivity.class.getName())).respondWith(new Instrumentation.ActivityResult(0, null));
onView(withContentDescription("Navigate up")).perform(click());
intended(hasComponent(ParentActivity.class.getName()));
}
Pamiętaj, że jest to obejście i będzie kolidować z innymi widokami o tym samym opisie treści.
Wykonywanie akcji w widoku
Możliwe jest wykonywanie funkcji ViewActions
w widoku za pomocą metody perform.
Klasa ViewActions
zapewnia metody pomocnicze dla najczęstszych akcji, takich jak:
ViewActions.click()
ViewActions.typeText()
ViewActions.clearText()
Na przykład, aby kliknąć widok:
onView(...).perform(click());
onView(withId(R.id.button_simple)).perform(click());
Możesz wykonać więcej niż jedną akcję, wykonując jedno połączenie:
onView(...).perform(typeText("Hello"), click());
Jeśli widok, z którym pracujesz, znajduje się w ScrollView
(pionowym lub poziomym), rozważ wcześniejsze działania wymagające wyświetlenia widoku (takie jak click()
i typeText()
) za pomocą scrollTo()
. Zapewnia to wyświetlenie widoku przed przejściem do innej akcji:
onView(...).perform(scrollTo(), click());
Znajdowanie widoku za pomocą onView
Dzięki ViewMatchers
możesz znaleźć widok w bieżącej hierarchii widoków.
Aby znaleźć widok, użyj metody onView()
z dopasowaniem widoku, który wybiera prawidłowy widok. Metody onView()
zwracają obiekt typu ViewInteraction
.
Na przykład znalezienie widoku przez jego R.id
jest tak proste, jak:
onView(withId(R.id.my_view))
Znajdowanie widoku z tekstem:
onView(withText("Hello World"))
Niestandardowe elementy dopasowujące espresso
Domyślnie Espresso ma wiele dopasowań, które pomagają znaleźć widoki, które należy wykonać, aby sprawdzić niektóre z nich.
Najważniejsze z nich można znaleźć w następującym ściągu:
https://google.github.io/android-testing-support-library/docs/espresso/cheatsheet/
Oto niektóre przykłady dopasowań:
- withId (R.id.ID_of_object_you_are_looking_for);
- withText („Niektóre teksty, które mają mieć obiekt”);
- isDisplayed () <- sprawdź, czy widok jest widoczny
- doesNotExist () <- sprawdź, czy widok nie istnieje
Wszystkie te są bardzo przydatne w codziennym użytkowaniu, ale jeśli masz bardziej złożone widoki, pisanie niestandardowych dopasowań może sprawić, że testy będą bardziej czytelne i możesz ich ponownie użyć w różnych miejscach.
Istnieją dwa najpopularniejsze typy dopasowywania, które można rozszerzyć: TypeSafeMatcher BoundedMatcher
Implementacja TypeSafeMatcher wymaga sprawdzenia instancji widoku, przeciwko któremu twierdzisz, jeśli jest to poprawny typ, dopasujesz niektóre jego właściwości do wartości podanej przez ciebie do dopasowywania.
Na przykład wpisz Bezpieczny moduł dopasowujący, który sprawdza poprawność widoku obrazu, ma poprawny rysunek:
public class DrawableMatcher extends TypeSafeMatcher<View> {
private @DrawableRes final int expectedId;
String resourceName;
public DrawableMatcher(@DrawableRes int expectedId) {
super(View.class);
this.expectedId = expectedId;
}
@Override
protected boolean matchesSafely(View target) {
//Type check we need to do in TypeSafeMatcher
if (!(target instanceof ImageView)) {
return false;
}
//We fetch the image view from the focused view
ImageView imageView = (ImageView) target;
if (expectedId < 0) {
return imageView.getDrawable() == null;
}
//We get the drawable from the resources that we are going to compare with image view source
Resources resources = target.getContext().getResources();
Drawable expectedDrawable = resources.getDrawable(expectedId);
resourceName = resources.getResourceEntryName(expectedId);
if (expectedDrawable == null) {
return false;
}
//comparing the bitmaps should give results of the matcher if they are equal
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
Bitmap otherBitmap = ((BitmapDrawable) expectedDrawable).getBitmap();
return bitmap.sameAs(otherBitmap);
}
@Override
public void describeTo(Description description) {
description.appendText("with drawable from resource id: ");
description.appendValue(expectedId);
if (resourceName != null) {
description.appendText("[");
description.appendText(resourceName);
description.appendText("]");
}
}
}
Użycie modułu dopasowywania może być opakowane w następujący sposób:
public static Matcher<View> withDrawable(final int resourceId) {
return new DrawableMatcher(resourceId);
}
onView(withDrawable(R.drawable.someDrawable)).check(matches(isDisplayed()));
Dopasowane ograniczenia są podobne, po prostu nie musisz sprawdzać typu, ale ponieważ jest to zrobione automatycznie:
/**
* Matches a {@link TextInputFormView}'s input hint with the given resource ID
*
* @param stringId
* @return
*/
public static Matcher<View> withTextInputHint(@StringRes final int stringId) {
return new BoundedMatcher<View, TextInputFormView>(TextInputFormView.class) {
private String mResourceName = null;
@Override
public void describeTo(final Description description) {
//fill these out properly so your logging and error reporting is more clear
description.appendText("with TextInputFormView that has hint ");
description.appendValue(stringId);
if (null != mResourceName) {
description.appendText("[");
description.appendText(mResourceName);
description.appendText("]");
}
}
@Override
public boolean matchesSafely(final TextInputFormView view) {
if (null == mResourceName) {
try {
mResourceName = view.getResources().getResourceEntryName(stringId);
} catch (Resources.NotFoundException e) {
throw new IllegalStateException("could not find string with ID " + stringId, e);
}
}
return view.getResources().getString(stringId).equals(view.getHint());
}
};
}
Więcej na temat dopasujących można przeczytać na:
https://developer.android.com/reference/android/support/test/espresso/matcher/ViewMatchers.html
Ogólnie Espresso
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'
ViewMatchers - Zbiór obiektów, które implementują Matcher<? super View>
interfejs. Możesz przekazać jeden lub więcej z nich do metody onView
, aby zlokalizować widok w bieżącej hierarchii widoków.
ViewActions - kolekcja ViewActions
które można przekazać do metody ViewInteraction.perform()
(na przykład click()
).
ViewAssertions - kolekcja ViewAssertions
które można przekazać za ViewInteraction.check()
metody ViewInteraction.check()
. Przez większość czasu będziesz używał asercji dopasowania, która używa dopasowywania widoku do potwierdzenia stanu aktualnie wybranego widoku.
Ściągawka do espresso firmy Google
Wpisz tekst w EditText
onView(withId(R.id.edt_name)).perform(typeText("XYZ"));
closeSoftKeyboard();
Wykonaj Kliknij na Widok
onView(withId(R.id.btn_id)).perform(click());
Sprawdzanie widoku jest wyświetlane
onView(withId(R.id.edt_pan_number)).check(ViewAssertions.matches((isDisplayed())));
Zgrupuj kolekcję klas testowych w zestawie testów
Możesz zorganizować wykonanie testowanych testów jednostkowych definiujących pakiet .
/**
* Runs all unit tests.
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({MyTest1.class ,
MyTest2.class,
MyTest3.class})
public class AndroidTestSuite {}
Następnie w AndroidStudio możesz uruchomić gradle lub ustawić nową konfigurację, taką jak:
Zestawy testowe można zagnieżdżać.