Upload
future-processing
View
103
Download
0
Embed Size (px)
Citation preview
Testy UI w Espresso z farmą w tle
O nas
Mateusz Boś
matbos
Michał Górski
mgorski-zlatan
O czym będziemy mówili?
● Espresso○ Custom View Action
○ Custom View Matcher
O czym będziemy mówili?
● Espresso○ Custom View Action
○ Custom View Matcher
● PageObject
O czym będziemy mówili?
● Espresso○ Custom View Action
○ Custom View Matcher
● PageObject
● Intent Matching/Stubing
O czym będziemy mówili?
● Espresso○ Custom View Action
○ Custom View Matcher
● PageObject
● Intent Matching/Stubing
● Idling Resource
O czym będziemy mówili?
● Espresso○ Custom View Action
○ Custom View Matcher
● PageObject
● Intent Matching/Stubing
● Idling Resource
● Testy
O czym będziemy mówili?
● Espresso○ Custom View Action
○ Custom View Matcher
● PageObject
● Intent Matching/Stubing
● Idling Resource
● Testy
● Uruchamianie testów w chmurze - AWS Device Farm i Firebase Test Lab
Espresso - Core API
Espresso.
Espresso - Core API
Espresso.onView()
Espresso - Core API
onView( )
Espresso - Core API
onView(withId(R.id.button))
Espresso - Core API
onView(withId(R.id.button))
ViewInteraction
ViewMatcher
Espresso - Core API
onView(withId(R.id.button))
ViewInteraction
ViewMatcher
NoMatchingViewException - gdy nie ma widoku, który opisaliśmy
Espresso - Core API
onView(withId(R.id.button))
ViewInteraction
ViewMatcher
NoMatchingViewException - gdy nie ma widoku, który opisaliśmy
AmbiguousViewMatcherException - gdy nasz opis pasuje do dwóch i więcej widoków
ViewMatchers
ViewInteraction loginButton = onView(withText("LOGGA IN"))
ViewMatchers
ViewInteraction passwordInput = onView(withHint("Lösenord"))
ViewMatchers
ViewInteraction loginButton = onView(withId(R.id.loginBtn))
Layout Inspector
ViewMatchers - złożone
allOf(Matcher<? super T>... matchers) - każdy z podanych
ViewMatchers - złożone
allOf(Matcher<? super T>... matchers) - każdy z podanych
allOf(
withText("Help me!"),
withHint("Attention"),
hasSibling(
withId(R.id.sos_button)
)
)
ViewMatchers - złożone
anyOf(Matcher<? super T>... matchers) - chociaż jeden z podanych
ViewMatchers - złożone
anyOf(Matcher<? super T>... matchers) - chociaż jeden z podanych
anyOf(
withText("Help me!"),
withHint("Attention"),
hasSibling(
withId(R.id.sos_button)
)
)
ViewMatchers
● isDisplayed
● isEnabled
● hasFocus
● isChecked
● withParent
● withChild
● hasIMEAction
● supportsInputMethod
● hasErrorText
● hasLinks
● isAssignableFrom
● isChecked
● isClickable
● ...
Espresso - Core API
onView(withId(R.id.button)).perform( );
Espresso - Core API
onView(withId(R.id.button)).perform(click());
Espresso - Core API
onView(withId(R.id.button)) .perform(click());
ViewInteraction
ViewActionViewMatcher
ViewActions
ViewInteraction loginButton = onView(withId(R.id.loginBtn))
loginButton.perform(click())
ViewActions
ViewInteraction passwordInput =
onView(withId(R.id.passwordTI))
passwordInput.perform(clearText())
passwordInput.perform(typeText(personalNumber),
closeSoftKeyboard())
ViewActions
● doubleClick
● longClick
● swipeLeft
● pressBack
● pressBackUnconditionally
● openLink
● pressKey
● pressMenuKey
● scrollTo
● swipeUp
● typeText
● ...
Espresso - Core API
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.msg))
Espresso - Core API
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.msg)).check( );
Espresso - Core API
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.msg)).check(matches( ));
Espresso - Core API
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.msg)).check(matches(isDisplayed()));
Espresso - Core API
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.msg)).check(matches(isDisplayed()));
ViewAssertion
ViewAssertions
ViewInteraction toggle = onView(withId(R.id.toggle))
toggle.check(matches(isChecked()))
ViewAssertions
● matches()
● doesNotExist()
● ViewMatchers
Espresso - Core API
ViewInteraction - Obiekt pośredni interakcji
ViewMatcher - pozwala znajdować widoki
ViewAction - pozwala wykonywać akcje na widokach
ViewAssertion - pozwala opisywać wymagania dla widoku
Cheat sheet - https://developer.android.com/training/testing/espresso/cheat-sheet.html
Custom ViewActions - scroll and click
Custom ViewActions - scroll and click
public static ViewAction scrollAndClick() {return new ViewAction() {
@Overridepublic String getDescription() {
return "Scroll and click";}
@Overridepublic Matcher<View> getConstraints() {
return ViewMatchers.isAssignableFrom(View.class);}
@Overridepublic void perform(UiController uiController, View view) {
scrollTo().perform(uiController, view);click().perform(uiController, view);
}
};}
Custom ViewActions - scroll and click
ViewInteraction button =
onView(withId(R.id.registerBtn))
button.perform(scrollAndClick())
Custom ViewActions - type text and close keyboard
Custom ViewActions - type text and close keyboard
public static ViewAction typeTextAndCloseKeyboard(String stringToBeTyped) {return new ViewAction() {
@Overridepublic void perform(UiController uiController, View view) {
scrollTo().perform(uiController, view);clearText().perform(uiController, view);if (stringToBeTyped.length() > 0) {
typeText(stringToBeTyped).perform(uiController, view);}closeSoftKeyboard().perform(uiController, view);
}…
};}
Custom ViewActions - type text and close keyboard
ViewInteraction passwordInput =
onView(withId(R.id.passwordTI))
passwordInput.perform(
typeTextAndCloseKeyboard())
Custom ViewActions - spinner
Custom ViewActions - spinner
void selectSpinnerItem(String text, Matcher<View> spinnerItemMatcher, ViewInteraction spinnerInteraction) {
spinnerInteraction.perform(scrollAndClick());try {
onData(is(text)).perform(click());} catch(Exception e) {
// if spinner was not opened properly we must retryselectSpinnerItem(text, spinnerItemMatcher, spinnerInteraction);
}
onView(spinnerItemMatcher)// if spinner was not checked properly we must retry.withFailureHandler((error, viewMatcher) -> selectSpinnerItem(...)).check(matches(withText(text)));
}
Custom ViewActions - spinner
selectSpinnerItem(otherLoansCost,withIdInParentWithId(R.id.otherLoansCostLSp, android.R.id.text1), otherLoansCostLSpinnerInteraction);
Custom ViewMatchers - podstawy
BaseMatcher<T> - Klasa bazowa dla wszystkich matcherów
Custom ViewMatchers - podstawy
BaseMatcher<T> - Klasa bazowa dla wszystkich matcherów
TypeSafeMatcher<T> - null safe
Custom ViewMatchers - podstawy
BaseMatcher<T> - Klasa bazowa dla wszystkich matcherów
TypeSafeMatcher<T> - null safe
BoundedMatcher<T,S extends T> - Matcher(z Espresso) dla typu T, który pozwala nam zawęzić
obiekt do pewnego podtypu T np. BoundedMatcher<View, TextView>
Custom ViewMatchers - visibility
public static Matcher<View> isVisible() {
return new TypeSafeMatcher<View>() {
@Override
public boolean matchesSafely(final View view){
return
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)
.matches(view);
}
…
};
}
Custom ViewMatchers - has background color
Matcher<View> hasBackgroundColor(@ColorRes final int colorId) {
return new TypeSafeMatcher<View>() {
@Override
public boolean matchesSafely(final View view) {
if(view instanceof CardView) {
return ((CardView)view).getCardBackgroundColor().getDefaultColor() ==
view.getContext().getResources().getColor(colorId);
}
return ((ColorDrawable) view.getBackground()).getColor() ==
view.getContext().getResources().getColor(colorId);
}
…
};
}
Custom ViewMatchers - ProgressBar ;)
public static Matcher<View> hasProgress(final int progress) {
return new BoundedMatcher<View, ProgressBar>(ProgressBar.class) {
@Override
public boolean matchesSafely(ProgressBar view) {
return view.getProgress() == progress;
}
…
};
}
Page Object
Jego zadaniem jest ukrycie niepotrzebnego skomplikowania dostępu do kontrolek, a uwypuklenie logiki
biznesowej
Page Object
Jego zadaniem jest ukrycie niepotrzebnego skomplikowania dostępu do kontrolek, a uwypuklenie logiki
biznesowej
Najczęściej reprezentuje tylko elementy ważne z punktu widzenia użytkownika(Nie musi wiernie
odwzorowywać całego ekranu)
Page Object
Jego zadaniem jest ukrycie niepotrzebnego skomplikowania dostępu do kontrolek, a uwypuklenie logiki
biznesowej
Najczęściej reprezentuje tylko elementy ważne z punktu widzenia użytkownika(Nie musi wiernie
odwzorowywać całego ekranu)
Powinien w odpowiedzi na metody zwracać albo typy proste albo inne Page Objecty
Page Object
Jego zadaniem jest ukrycie niepotrzebnego skomplikowania dostępu do kontrolek, a uwypuklenie logiki
biznesowej
Najczęściej reprezentuje tylko elementy ważne z punktu widzenia użytkownika(Nie musi wiernie
odwzorowywać całego ekranu)
Powinien w odpowiedzi na metody zwracać albo typy proste albo inne Page Objecty
Może ale nie musi zawierać asercje dot. danego ekranu (zdania są podzielone)
Page Object
Page Object
@Testpublic void openRegisterScreen() {
onView(ViewMatchers.withId(R.id.registerButton))
.perform(click());
}
Page Object
@Testpublic void openRegisterScreen() {
onView(ViewMatchers.withId(R.id.registerButton))
.perform(click());
onView(ViewMatchers.withId(R.id. newUsernameEditText))
.check(matches(isDisplayed()));
}
Page Object
@Testpublic void openRegisterScreen() {
onView(ViewMatchers.withId(R.id.registerButton))
.perform(click());
onView(ViewMatchers.withId(R.id. newUsernameEditText))
.check(matches(isDisplayed()));
Espresso.closeSoftKeyboard();
}
Page Object
@Testpublic void openRegisterScreen() {
onView(ViewMatchers.withId(R.id.registerButton))
.perform(click());
onView(ViewMatchers.withId(R.id. newUsernameEditText))
.check(matches(isDisplayed()));
Espresso.closeSoftKeyboard();
Espresso.pressBack();
}
Page Object
@Testpublic void openRegisterScreen() {
onView(ViewMatchers.withId(R.id.registerButton))
.perform(click());
onView(ViewMatchers.withId(R.id. newUsernameEditText))
.check(matches(isDisplayed()));
Espresso.closeSoftKeyboard();
Espresso.pressBack();
onView(ViewMatchers.withId(R.id.usernameEditText))
.check(matches(isDisplayed()));
}
Page Object
@Testpublic void openRegisterScreen() {
onView(ViewMatchers.withId(R.id.registerButton))
.perform(click());
onView(ViewMatchers.withId(R.id. newUsernameEditText))
.check(matches(isDisplayed()));
Espresso.closeSoftKeyboard();
Espresso.pressBack();
onView(ViewMatchers.withId(R.id.usernameEditText))
.check(matches(isDisplayed()));
}
@Testpublic void openRegisterScreen() {
new LoginPageObject().openRegisterScreen().validate().goBack().validate();
}
Page Objectpublic class LoginPageObject {
private final ViewInteraction usernameEditText;
private final ViewInteraction passwordEditText;
private final ViewInteraction loginButton;
private final ViewInteraction registerButton;
public LoginPageObject() {
usernameEditText = onView(withId(R.id.usernameEditText));
passwordEditText = onView(withId(R.id.passwordEditText));
loginButton = onView(withId(R.id.loginButton));
registerButton = onView(withId(R.id.registerButton));
}
Page Objectpublic class LoginPageObject {
private final ViewInteraction usernameEditText;
private final ViewInteraction passwordEditText;
private final ViewInteraction loginButton;
private final ViewInteraction registerButton;
public LoginPageObject() {
usernameEditText = onView(withId(R.id.usernameEditText));
passwordEditText = onView(withId(R.id.passwordEditText));
loginButton = onView(withId(R.id.loginButton));
registerButton = onView(withId(R.id.registerButton));
}
public ItemListPageObject login(String username, String password){
usernameEditText.perform(typeText(username));
passwordEditText.perform(typeText(password));
loginButton.perform(click());
return new ItemListPageObject();
}
Page Objectpublic class LoginPageObject {
private final ViewInteraction usernameEditText;
private final ViewInteraction passwordEditText;
private final ViewInteraction loginButton;
private final ViewInteraction registerButton;
public LoginPageObject() {
usernameEditText = onView(withId(R.id.usernameEditText));
passwordEditText = onView(withId(R.id.passwordEditText));
loginButton = onView(withId(R.id.loginButton));
registerButton = onView(withId(R.id.registerButton));
}
public ItemListPageObject login(String username, String password) {
usernameEditText.perform(typeText(username));
passwordEditText.perform(typeText(password));
loginButton.perform(click());
return new ItemListPageObject();
}
public RegisterPageObject openRegisterScreen() {
registerButton.perform(click());
return new RegisterPageObject();
}
Page Objectpublic class LoginPageObject {
private final ViewInteraction usernameEditText;
private final ViewInteraction passwordEditText;
private final ViewInteraction loginButton;
private final ViewInteraction registerButton;
public LoginPageObject() {
usernameEditText = onView(withId(R.id.usernameEditText));
passwordEditText = onView(withId(R.id.passwordEditText));
loginButton = onView(withId(R.id.loginButton));
registerButton = onView(withId(R.id.registerButton));
}
public ItemListPageObject login(String username, String password) {
usernameEditText.perform(typeText(username));
passwordEditText.perform(typeText(password));
loginButton.perform(click());
return new ItemListPageObject();
}
public RegisterPageObject openRegisterScreen() {
registerButton.perform(click());
return new RegisterPageObject();
}
public LoginPageObject validate() {
usernameEditText.check(matches(isDisplayed()));
passwordEditText.check(matches(isDisplayed()));
passwordEditText.check(matches(withInputType(INPUT_TYPE)));
loginButton.check(matches(isDisplayed()));
registerButton.check(matches(isDisplayed()));
return this;
}
Page Objectpublic class LoginPageObject {
private final ViewInteraction usernameEditText;
private final ViewInteraction passwordEditText;
private final ViewInteraction loginButton;
private final ViewInteraction registerButton;
public LoginPageObject() {
usernameEditText = onView(withId(R.id.usernameEditText));
passwordEditText = onView(withId(R.id.passwordEditText));
loginButton = onView(withId(R.id.loginButton));
registerButton = onView(withId(R.id.registerButton));
}
public ItemListPageObject login(String username, String password) {
usernameEditText.perform(typeText(username));
passwordEditText.perform(typeText(password));
loginButton.perform(click());
return new ItemListPageObject();
}
public RegisterPageObject openRegisterScreen() {
registerButton.perform(click());
return new RegisterPageObject();
}
public LoginPageObject validate() {
usernameEditText.check(matches(isDisplayed()));
passwordEditText.check(matches(isDisplayed()));
passwordEditText.check(matches(withInputType(INPUT_TYPE)));
loginButton.check(matches(isDisplayed()));
registerButton.check(matches(isDisplayed()));
return this;
}
public LoginPageObject loginWithError(String username,
String password) {
login(username, password);
return this;
}
Page Objectpublic class LoginPageObject {
private final ViewInteraction usernameEditText;
private final ViewInteraction passwordEditText;
private final ViewInteraction loginButton;
private final ViewInteraction registerButton;
public LoginPageObject() {
usernameEditText = onView(withId(R.id.usernameEditText));
passwordEditText = onView(withId(R.id.passwordEditText));
loginButton = onView(withId(R.id.loginButton));
registerButton = onView(withId(R.id.registerButton));
}
public ItemListPageObject login(String username, String password) {
usernameEditText.perform(typeText(username));
passwordEditText.perform(typeText(password));
loginButton.perform(click());
return new ItemListPageObject();
}
public RegisterPageObject openRegisterScreen() {
registerButton.perform(click());
return new RegisterPageObject();
}
public LoginPageObject validate() {
usernameEditText.check(matches(isDisplayed()));
passwordEditText.check(matches(isDisplayed()));
passwordEditText.check(matches(withInputType(INPUT_TYPE)));
loginButton.check(matches(isDisplayed()));
registerButton.check(matches(isDisplayed()));
return this;
}
public LoginPageObject loginWithError(String username, String password) {
login(username, password);
return this;
}
public LoginPageObject validateError() {
onView(withText(R.string.loginError)).check(matches(isDisplayed()));
return this;
}
}
Klasa testowa - JUnit 4
@Rule - dostarcza rozszerzalny mechanizm zmiany zachowania testów
ActivityTestRule - zapewnia “świeże” activity przed każdym testem
ServiceTestRule - zapewnia “świeży” serwis przed każdym testem
Klasa testowa - JUnit 4
@Rule - dostarcza rozszerzalny mechanizm zmiany zachowania testów
ActivityTestRule - zapewnia “świeże” activity przed każdym testem
ServiceTestRule - zapewnia “świeży” serwis przed każdym testem
@RunWith - pozwala ustawić runner dla klasy testowej
Klasa testowa - JUnit 4
@Rule - dostarcza rozszerzalny mechanizm zmiany zachowania testów
ActivityTestRule - zapewnia “świeże” activity przed każdym testem
ServiceTestRule - zapewnia “świeży” serwis przed każdym testem
@RunWith - pozwala ustawić runner dla klasy testowej
@Before - oznaczenie metody wywoływanej przed każdym testem
Klasa testowa - JUnit 4
@Rule - dostarcza rozszerzalny mechanizm zmiany zachowania testów
ActivityTestRule - zapewnia “świeże” activity przed każdym testem
ServiceTestRule - zapewnia “świeży” serwis przed każdym testem
@RunWith - pozwala ustawić runner dla klasy testowej
@Before - oznaczenie metody wywoływanej przed każdym testem
@After - oznaczenie metody wywoływanej po każdym teście
Klasa testowa - JUnit 4
@Rule - dostarcza rozszerzalny mechanizm zmiany zachowania testów
ActivityTestRule - zapewnia “świeże” activity przed każdym testem
ServiceTestRule - zapewnia “świeży” serwis przed każdym testem
@RunWith - pozwala ustawić runner dla klasy testowej
@Before - oznaczenie metody wywoływanej przed każdym testem
@After - oznaczenie metody wywoływanej po każdym teście
@Test - oznaczenie metody testowej
@RunWith(AndroidJUnit4.class)public class IncreaseCreditLineLimitTests {
@Rule public ActivityTestRule<LauncherActivity> activityRule = new ActivityTestRule<>(LauncherActivity.class, false, false);
…
@Beforepublic void setUp() {
TestCredwayApp.reloadComponent();TestCredwayApp.getComponent().inject(this);Espresso.registerIdlingResources(idlingResource);
userSessionSimulator.cleanSession();}
@Afterpublic void tearDown() {
Espresso.unregisterIdlingResources(idlingResource);}
@Testpublic void increaseLimitSuccessWithRatingDialog() {
String currentCreditLineLimitText = "15 000 kr";String chosenCreditLineLimitText = "16 000 kr";…
}}
Intent
Weryfikacja intentów(matching) - sprawdzenie czy dany Intent został wysłany
intended(allOf(
hasAction(equalTo(Intent.ACTION_VIEW)),
hasCategories(hasItem(equalTo(Intent.CATEGORY_BROWSABLE))),
hasData(hasHost(equalTo("www.google.com"))),
hasExtras(allOf(
hasEntry(equalTo("key1"), equalTo("value1")),
hasEntry(equalTo("key2"), equalTo("value2")))),
toPackage("com.android.browser")));
Intent
Stubowanie intentów, pozwala zwrócić odpowiednio spreparowaną odpowiedź na
dopasowany intent.
Intent resultData = new Intent();
String phoneNumber = "123-345-6789";
resultData.putExtra("phone", phoneNumber);
ActivityResult result = new ActivityResult(Activity.RESULT_OK, resultData);
intending(toPackage("com.android.contacts")).respondWith(result);
Intent
@Rule public IntentsTestRule<MyPagesActivity> testRule = new IntentsTestRule<>(MyPagesActivity.class, true, false);
@Inject UserSessionSimulator userSessionSimulator;
@Beforepublic void setUp() {
TestCredwayApp.reloadComponent();TestCredwayApp.getComponent().inject(this);userSessionSimulator.simulateUserSession();
}
@Testpublic void shouldStartDialerIntent() {
testRule.launchActivity(null);mockDialerIntent();new MyPagesPageObject()
.openDrawer()
.openPhone();verifyCalledDialerIntentWithPhoneUri("tel:08363773");
}
Intent
@Rule public IntentsTestRule<MyPagesActivity> testRule = new IntentsTestRule<>(MyPagesActivity.class, true, false);
@Inject UserSessionSimulator userSessionSimulator;
@Beforepublic void setUp() {
TestCredwayApp.reloadComponent();TestCredwayApp.getComponent().inject(this);userSessionSimulator.simulateUserSession();
}
@Testpublic void shouldStartDialerIntent() {
testRule.launchActivity(null);mockDialerIntent();new MyPagesPageObject()
.openDrawer()
.openPhone();verifyCalledDialerIntentWithPhoneUri("tel:08363773");
}
Intent
@Rule public IntentsTestRule<MyPagesActivity> testRule = new IntentsTestRule<>(MyPagesActivity.class, true, false);
@Testpublic void shouldStartDialerIntent() {
testRule.launchActivity(null);
mockDialerIntent();
new MyPagesPageObject().openDrawer().openPhone();
verifyCalledDialerIntentWithPhoneUri("tel:08363773");
}
Intent
@Rule public IntentsTestRule<MyPagesActivity> testRule = new IntentsTestRule<>(MyPagesActivity.class, true, false);
@Testpublic void shouldStartDialerIntent() {
testRule.launchActivity(null);
intending(hasAction(Intent.ACTION_DIAL)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
new MyPagesPageObject().openDrawer().openPhone();
intended(allOf(
hasAction(Intent.ACTION_DIAL), hasData("tel:08363773")
));
}
Idling Resource
Dostępne są cztery podstawowe klasy pozwalające na synchronizację Espresso z
naszym kodem:
● CountingIdlingResource
Idling Resource
Dostępne są cztery podstawowe klasy pozwalające na synchronizację Espresso z
naszym kodem:
● CountingIdlingResource
● UriIdlingResource
Idling Resource
Dostępne są cztery podstawowe klasy pozwalające na synchronizację Espresso z
naszym kodem:
● CountingIdlingResource
● UriIdlingResource
● IdlingThreadPoolExecutor
Idling Resource
Dostępne są cztery podstawowe klasy pozwalające na synchronizację Espresso z
naszym kodem:
● CountingIdlingResource
● UriIdlingResource
● IdlingThreadPoolExecutor
● IdlingScheduledThreadPoolExecutor
Idling Resources public final class RxEspressoTransformer {
private final Observable.Transformer transformer;
public RxEspressoTransformer(CountingIdlingResource idlingResource) {
transformer = new Observable.Transformer<Observable, Observable>() {
@Override
public Observable<Observable> call(Observable<Observable> observableObservable) {
return observableObservable
.doOnSubscribe(idlingResource::increment)
.doAfterTerminate(idlingResource::decrement);
}
};
}
public <T> Observable.Transformer<T, T> apply() {
return (Observable.Transformer<T, T>) transformer;
}
}
Idling Resources public final class RxEspressoTransformer {
private final Observable.Transformer transformer;
public RxEspressoTransformer(CountingIdlingResource idlingResource) {
transformer = new Observable.Transformer<Observable, Observable>() {
@Override
public Observable<Observable> call(Observable<Observable> observableObservable) {
return observableObservable
.doOnSubscribe(idlingResource::increment)
.doAfterTerminate(idlingResource::decrement);
}
};
}
public <T> Observable.Transformer<T, T> apply() {
return (Observable.Transformer<T, T>) transformer;
}
}
Idling Resources
observable
.compose(espressoTransformer.apply())
…
public final class RxEspressoTransformer {
private final Observable.Transformer transformer;
public RxEspressoTransformer(CountingIdlingResource idlingResource) {
transformer = new Observable.Transformer<Observable, Observable>() {
@Override
public Observable<Observable> call(Observable<Observable> observableObservable) {
return observableObservable
.doOnSubscribe(idlingResource::increment)
.doAfterTerminate(idlingResource::decrement);
}
};
}
public <T> Observable.Transformer<T, T> apply() {
return (Observable.Transformer<T, T>) transformer;
}
}
Idling Resources
observable
.compose(espressoTransformer.apply())
…
public final class RxEspressoTransformer {
private final Observable.Transformer transformer;
public RxEspressoTransformer(CountingIdlingResource idlingResource) {
transformer = new Observable.Transformer<Observable, Observable>() {
@Override
public Observable<Observable> call(Observable<Observable> observableObservable) {
return observableObservable
.doOnSubscribe(idlingResource::increment)
.doAfterTerminate(idlingResource::decrement);
}
};
}
public <T> Observable.Transformer<T, T> apply() {
return (Observable.Transformer<T, T>) transformer;
}
}
public class IncreaseCreditLineLimitTests {
…
@Inject CountingIdlingResource idlingResource;
@Before
public void setUp() {
…
Espresso.registerIdlingResources(idlingResource);
…
}
…
}
Idling Resources
observable
.compose(espressoTransformer.apply())
…
public final class RxEspressoTransformer {
private final Observable.Transformer transformer;
public RxEspressoTransformer(CountingIdlingResource idlingResource) {
transformer = new Observable.Transformer<Observable, Observable>() {
@Override
public Observable<Observable> call(Observable<Observable> observableObservable) {
return observableObservable
.doOnSubscribe(idlingResource::increment)
.doAfterTerminate(idlingResource::decrement);
}
};
}
public <T> Observable.Transformer<T, T> apply() {
return (Observable.Transformer<T, T>) transformer;
}
}
public class IncreaseCreditLineLimitTests {
…
@Inject CountingIdlingResource idlingResource;
@Before
public void setUp() {
…
Espresso.registerIdlingResources(idlingResource);
…
}
…
}
Test ekranu formularza
@Rule public ActivityTestRule<ApplyFormActivity> activityRule = new ActivityTestRule<>(ApplyFormActivity.class);
…
@Testpublic void validateShowErrorOnWrongEmail() {
new ApplyFormPageObject().typePersonalNumber("198911069998").typeMobileNumber("0786545678").typeEmail("test@email").typeClearingNumber("5555").typeAccountNumber("8911069998").selectIncome("1 000 kr+").selectHousing("Home").selectHousingCost("1 000 kr+").selectOtherLoansCost("1 000 kr+").selectEmploymentForm("Freelancer").selectChildrenCount("0+").clickOnApplyButtonWrongInput().validateWrongEmailError();
}
Test procesu rejestracji
@Rule public ActivityTestRule<LauncherActivity> activityRule = new ActivityTestRule<>(LauncherActivity.class);
@Testpublic void applyForCreditLineConfirmationStep() {
new WelcomePageObject().validate().clickOnRegistrationButton().validate("30 000 kr", 21, 21).clickOnApplyButton().validate("30 000 kr", "10 kr")
… (wypełnianie formularza tak samo jak w poprzednim teście) …
.clickOnPositiveButtonConfirmation()
.validate()
.onBackClick()
.validate();}
AWS Device Farm && Firebase Test Lab
Firebase Test Lab
Pytania
Useful resources
Espresso:
https://developer.android.com/training/testing/espresso/index.html
Espresso-Intents:
https://developer.android.com/training/testing/espresso/intents.html
Espresso-Cheat Sheet
https://developer.android.com/training/testing/espresso/cheat-sheet.html
Dzięki za uwagę!