espresso devoxx 2014
DESCRIPTION
Your Robotium tests are flaky? not readable? Come to this Tools in Action and discover Espresso, the new testing library for Android. During this live coding presentation, you will learn how to write beautiful tests and take advantage of Espresso features. Par Thomas Guérin, consultant chez Xebia, à Devoxx Belgique 2014.TRANSCRIPT
@Tom404_#Devoxx #AndroidEspresso
Espresso: What else ?Thomas Guerin Xebia France
@Tom404_#Devoxx #AndroidEspresso
@Tom404_#Devoxx #AndroidEspresso
“My tests last hours !”
@Tom404_#Devoxx #AndroidEspresso
“My tests last hours !”
“Testing is sometimes complicated !”
@Tom404_#Devoxx #AndroidEspresso
“My tests last hours !”
“Testing is sometimes complicated !”
“My tests randomly fail !”
@Tom404_#Devoxx #AndroidEspresso
“My tests last hours !”
“Testing is sometimes complicated !”
“My tests randomly fail !”
“I have to fix the tests for a simple modification in my code”
@Tom404_#Devoxx #AndroidEspresso
“My tests last hours !”
“Testing is sometimes complicated !”
“My tests randomly fail !”
“I have to fix the tests for a simple modification in my code”
@Tom404_#Devoxx #AndroidEspresso
“My tests last hours !”
“Testing is sometimes complicated !”
“My tests randomly fail !”
“I have to fix the tests for a simple modification in my code”
@Tom404_#Devoxx #AndroidEspresso
“My tests last hours !”
“Testing is sometimes complicated !”
“My tests randomly fail !”
“I have to fix the tests for a simple modification in my code”
@Tom404_#Devoxx #AndroidEspresso
A test must be …
@Tom404_#Devoxx #AndroidEspresso
A test must be …
Simple
@Tom404_#Devoxx #AndroidEspresso
A test must be …
FastSimple
@Tom404_#Devoxx #AndroidEspresso
A test must be …
FastSimple Reliable
@Tom404_#Devoxx #AndroidEspresso
A test must be …
FastSimple Reliable Durable
@Tom404_#Devoxx #AndroidEspresso
// Start the main activity of the application under test mActivity = getActivity();
// Get a handle to the Activity object's main UI widget, a Spinner mSpinner = (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner01);
// Set the Spinner to a known position mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
// Stop the activity -‐ The onDestroy() method should save the state of the Spinner mActivity.finish();
// Re-‐start the Activity -‐ the onResume() method should restore the state of the Spinner mActivity = getActivity();
// Get the Spinner's current position int currentPosition = mActivity.getSpinnerPosition();
// Assert that the current position is the same as the starting position assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
Bad example
@Tom404_#Devoxx #AndroidEspresso
// Start the main activity of the application under test mActivity = getActivity();
// Get a handle to the Activity object's main UI widget, a Spinner mSpinner = (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner01);
// Set the Spinner to a known position mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
// Stop the activity -‐ The onDestroy() method should save the state of the Spinner mActivity.finish();
// Re-‐start the Activity -‐ the onResume() method should restore the state of the Spinner mActivity = getActivity();
// Get the Spinner's current position int currentPosition = mActivity.getSpinnerPosition();
// Assert that the current position is the same as the starting position assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
Bad example
@Tom404_#Devoxx #AndroidEspresso
Robotium
@Tom404_#Devoxx #AndroidEspresso
API Overview
@Tom404_#Devoxx #AndroidEspresso
API Overview
Solo
getView(int viewId)
drag(…)
clickInList(int line, int index)
goBack()setNavigationDrawer(int status)pressSoftKeyboardNextButton()
clickOnActionBarItem(int id)
scrollToBottom() searchEditText(String text)
pressSoftKeyboardSearchButton()
getButton(int index)scrollDown()clearEditText(int index)
clickOnToggleButton(String text)
clickOnImageButton(int index)
clickOnButton(int index)
pressMenuItem(int index)
clickOnActionBarHome()
scrollDownList(int index)
searchText(String text)
@Tom404_#Devoxx #AndroidEspresso
solo.clickOnView(R.id.start_activity_button);
// Wait for a specific condition to occur solo.waitForCondition(new Condition() { @Override public boolean isSatisfied() { return checkConditionInNewActivity(); } }, Timeout.getSmallTimeout());
solo.clickOnView(R.id.view_in_new_activity);
Basic example
@Tom404_#Devoxx #AndroidEspresso
solo.clickOnView(R.id.start_activity_button);
// Wait for a specific condition to occur solo.waitForCondition(new Condition() { @Override public boolean isSatisfied() { return checkConditionInNewActivity(); } }, Timeout.getSmallTimeout());
solo.clickOnView(R.id.view_in_new_activity);
Basic example
@Tom404_#Devoxx #AndroidEspresso
Robotium drawbacksFlaky tests
Hard to extend
View index based methods
Errors not meaningful
@Tom404_#Devoxx #AndroidEspresso
Espresso
@Tom404_#Devoxx #AndroidEspresso
Hamcrest
@Tom404_#Devoxx #AndroidEspresso
HamcrestLibrary of matchers
@Tom404_#Devoxx #AndroidEspresso
HamcrestLibrary of matchers
assertThat("Hello", equalTo("Hello"))
// Syntaxic sugar assertThat("Hello", is("Hello"))
// A lot of matchers available allOf not instanceOf hasProperty equalToIgnoringCase
@Tom404_#Devoxx #AndroidEspresso
HamcrestLibrary of matchers
assertThat("Hello", equalTo("Hello"))
// Syntaxic sugar assertThat("Hello", is("Hello"))
// A lot of matchers available allOf not instanceOf hasProperty equalToIgnoringCase
Easy to compose
@Tom404_#Devoxx #AndroidEspresso
public static Matcher<View> isVisible() { return new TypeSafeMatcher<View>() { @Override public void describeTo(Description description) { description.appendText("is view visible"); }
@Override public boolean matchesSafely(View view) { return view.getVisibility() == View.VISIBLE; } }; }
Custom matcher
@Tom404_#Devoxx #AndroidEspresso
API overview
@Tom404_#Devoxx #AndroidEspresso
API overview
onView(Matcher<View>)onData(Matcher<Object>
Espresso
@Tom404_#Devoxx #AndroidEspresso
API overview
onView(Matcher<View>)onData(Matcher<Object>
Espresso withId()withText(text)
hasSibling(Matcher<View>)
ViewMatchers
@Tom404_#Devoxx #AndroidEspresso
API overview
onView(Matcher<View>)onData(Matcher<Object>
Espresso
perform(ViewAction)check(ViewAssertion)
ViewInteraction / DataInteraction
withId()withText(text)
hasSibling(Matcher<View>)
ViewMatchers
@Tom404_#Devoxx #AndroidEspresso
API overview
onView(Matcher<View>)onData(Matcher<Object>
Espresso
perform(ViewAction)check(ViewAssertion)
ViewInteraction / DataInteraction
click()typeText(text)
ViewActions
withId()withText(text)
hasSibling(Matcher<View>)
ViewMatchers
@Tom404_#Devoxx #AndroidEspresso
API overview
onView(Matcher<View>)onData(Matcher<Object>
Espresso
perform(ViewAction)check(ViewAssertion)
ViewInteraction / DataInteraction
click()typeText(text)
ViewActions
doesNotExist()matches(Matcher<View>)
ViewAssertions
withId()withText(text)
hasSibling(Matcher<View>)
ViewMatchers
@Tom404_#Devoxx #AndroidEspresso
Goodbye flakiness
@Tom404_#Devoxx #AndroidEspresso
Goodbye flakinessFor each test action, Espresso will :
@Tom404_#Devoxx #AndroidEspresso
Goodbye flakinessFor each test action, Espresso will :
1. Wait until the app is idle
@Tom404_#Devoxx #AndroidEspresso
Goodbye flakinessFor each test action, Espresso will :
1. Wait until the app is idle2. Run on the UI thread
@Tom404_#Devoxx #AndroidEspresso
Goodbye flakinessFor each test action, Espresso will :
1. Wait until the app is idle2. Run on the UI thread3. Wait until completion
@Tom404_#Devoxx #AndroidEspresso
Goodbye flakinessFor each test action, Espresso will :
1. Wait until the app is idle2. Run on the UI thread3. Wait until completion4. Check the result
@Tom404_#Devoxx #AndroidEspresso
Goodbye flakinessFor each test action, Espresso will :
1. Wait until the app is idle2. Run on the UI thread3. Wait until completion4. Check the result
Demo
@Tom404_#Devoxx #AndroidEspresso