Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Efficient UI testing Android apps by example

Efficient UI testing Android apps by example

This talk will cover:
- analyzing general use cases which apply to many applications
- tools which can help us with efficient testing
- efficient combination of different types of UI tests
- best practices for adding and maintaining test cases to your project

Alex Zhukovich

October 20, 2018
Tweet

More Decks by Alex Zhukovich

Other Decks in Technology

Transcript

  1. @RunWith(AndroidJUnit4::class) class SignInActivityTest { private val correctEmail = "[email protected]" @Rule

    @JvmField val activityRule = ActivityTestRule<SignInActivity>(SignInActivity::class.java) @Test fun shouldDisplayPasswordErrorWhenPasswordIsEmpty() { onView(withId(R.id.email)) .perform(replaceText(correctEmail)) onView(withId(R.id.signIn)) .perform(click()) onView(withText(R.string.error_password_should_not_be_empty)) .check(matches(isDisplayed())) } }
  2. Espresso Test Recorder @LargeTest @RunWith(AndroidJUnit4::class) class SignInActivityTest { @Rule @JvmField

    var mActivityTestRule = ActivityTestRule(SignInActivity::class.java) @Test fun signInActivityTest() { Thread.sleep(7000) val appCompatEditText = onView( allOf(withId(R.id.email), childAtPosition( allOf(withId(R.id.signInRoot), childAtPosition( withId(android.R.id.content), 0)), 4), isDisplayed())) appCompatEditText.perform(click()) val appCompatEditText2 = onView( allOf(withId(R.id.email), childAtPosition( allOf(withId(R.id.signInRoot), childAtPosition( withId(android.R.id.content), 0)), 4), isDisplayed())) appCompatEditText2.perform(replaceText("[email protected]"), closeSoftKeyboard()) Thread.sleep(7000) pressBack() val appCompatButton = onView( allOf(withId(R.id.signIn), withText("Sing In"), childAtPosition( allOf(withId(R.id.signInRoot), childAtPosition( withId(android.R.id.content), 0)), 6), isDisplayed())) appCompatButton.perform(click()) } private fun childAtPosition( parentMatcher: Matcher<View>, position: Int): Matcher<View> { return object : TypeSafeMatcher<View>() { override fun describeTo(description: Description) { description.appendText("Child at position $position in parent ") parentMatcher.describeTo(description) } public override fun matchesSafely(view: View): Boolean { val parent = view.parent return parent is ViewGroup && parentMatcher.matches(parent) && view == parent.getChildAt(position) } } } }
  3. @RunWith(AndroidJUnit4::class) class SmokeTests { private val correctEmail = "[email protected]" private

    val correctPassword = "test123" @Rule @JvmField val chain: RuleChain = RuleChain .outerRule(GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)) .around(ActivityTestRule<SplashActivity>(SplashActivity::class.java, true, false)) @Test fun shouldVerifySuccessfulLogin() { val mapVisibilityIdlingResource = ViewVisibilityIdlingResource(R.id.mapContainer, View.VISIBLE) splashActivityE2ETestRule.launchActivity(null) onView(withId(R.id.signIn)) .perform(click()) onView(withId(R.id.email)) .perform(replaceText(correctEmail), closeSoftKeyboard()) onView(withId(R.id.password)) .perform(replaceText(correctPassword), closeSoftKeyboard()) onView(withId(R.id.signIn)) .perform(click()) IdlingRegistry.getInstance().register(mapVisibilityIdlingResource) onView(withId(R.id.mapContainer)) .check(ViewAssertions.matches(isDisplayed())) IdlingRegistry.getInstance().unregister(mapVisibilityIdlingResource) openActionBarOverflowOrOptionsMenu(getActivityInstance()) onView(withText(R.string.nav_sign_out_title)) .check(ViewAssertions.matches(isDisplayed())) .perform(click()) } }
  4. open class BaseTestRobot { fun enterText(viewId: Int, text: String): ViewInteraction

    = onView(withId(viewId)) .perform(replaceText(text), closeSoftKeyboard()) fun clickView(viewId: Int): ViewInteraction = onView(withId(viewId)) .perform(click()) } onView(withId(R.id.email)) .perform(replaceText(email), closeSoftKeyboard()) onView(withId(R.id.password)) .perform(replaceText(password), closeSoftKeyboard()) onView(withId(R.id.signIn)) .perform(click())
  5. class SignInScreenRobot : BaseTestRobot() { fun signIn(email: String, password: String)

    { enterText(R.id.email, email) enterText(R.id.password, password) clickView(R.id.signIn) } } open class BaseTestRobot { fun enterText(viewId: Int, text: String): ViewInteraction = onView(withId(viewId)) .perform(replaceText(text), closeSoftKeyboard()) fun clickView(viewId: Int): ViewInteraction = onView(withId(viewId)) .perform(click()) } onView(withId(R.id.email)) .perform(replaceText(email), closeSoftKeyboard()) onView(withId(R.id.password)) .perform(replaceText(password), closeSoftKeyboard()) onView(withId(R.id.signIn)) .perform(click())
  6. class SignInScreenRobot : BaseTestRobot() { fun signIn(email: String, password: String)

    { enterText(R.id.email, email) enterText(R.id.password, password) clickView(R.id.signIn) } } open class BaseTestRobot { fun enterText(viewId: Int, text: String): ViewInteraction = onView(withId(viewId)) .perform(replaceText(text), closeSoftKeyboard()) fun clickView(viewId: Int): ViewInteraction = onView(withId(viewId)) .perform(click()) } fun signInScreen(func: SignInScreenRobot.() -> Unit) = SignInScreenRobot().apply { func() } onView(withId(R.id.email)) .perform(replaceText(email), closeSoftKeyboard()) onView(withId(R.id.password)) .perform(replaceText(password), closeSoftKeyboard()) onView(withId(R.id.signIn)) .perform(click())
  7. class SignInScreenRobot : BaseTestRobot() { fun signIn(email: String, password: String)

    { enterText(R.id.email, email) enterText(R.id.password, password) clickView(R.id.signIn) } } open class BaseTestRobot { fun enterText(viewId: Int, text: String): ViewInteraction = onView(withId(viewId)) .perform(replaceText(text), closeSoftKeyboard()) fun clickView(viewId: Int): ViewInteraction = onView(withId(viewId)) .perform(click()) } fun signInScreen(func: SignInScreenRobot.() -> Unit) = SignInScreenRobot().apply { func() } signInScreen { signIn(email, password) } onView(withId(R.id.email)) .perform(replaceText(email), closeSoftKeyboard()) onView(withId(R.id.password)) .perform(replaceText(password), closeSoftKeyboard()) onView(withId(R.id.signIn)) .perform(click())
  8. @RunWith(AndroidJUnit4::class) class SmokeTests { private val correctEmail = "[email protected]" private

    val correctPassword = "test123" @Rule @JvmField val chain: RuleChain = RuleChain .outerRule(GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)) .around(ActivityTestRule<SplashActivity>(SplashActivity::class.java, true, false)) @Test fun shouldVerifySuccessfulLogin() { val mapVisibilityIdlingResource = ViewVisibilityIdlingResource(R.id.mapContainer, View.VISIBLE) splashActivityE2ETestRule.launchActivity(null) onView(withId(R.id.signIn)) .perform(click()) onView(withId(R.id.email)) .perform(replaceText(correctEmail), closeSoftKeyboard()) onView(withId(R.id.password)) .perform(replaceText(correctPassword), closeSoftKeyboard()) onView(withId(R.id.signIn)) .perform(click()) IdlingRegistry.getInstance().register(mapVisibilityIdlingResource) onView(withId(R.id.mapContainer)) .check(ViewAssertions.matches(isDisplayed())) IdlingRegistry.getInstance().unregister(mapVisibilityIdlingResource) openActionBarOverflowOrOptionsMenu(getActivityInstance()) onView(withText(R.string.nav_sign_out_title)) .check(ViewAssertions.matches(isDisplayed())) .perform(click()) } } @Test fun shouldVerifySuccessfulLogin() { splashScreen { display() } loginScreen { openSignIn() } signInScreen { signIn(email, password) } homeScreen { isMapDisplayed() signOut() } }
  9. Testing interaction with a server Mock layer Interaction with server(s)

    Mocking interaction with server WireMock RestMock MockWebServer
  10. Authorization of the user - scenarios Test scenario #1: Enter

    correct auth data Test scenario #2: Enter incorrect auth data Test scenario #3: Enter incorrect auth data and handle them on client side
  11. User Authorization End To End @Test fun shouldVerifyUserAuthorization() { loginScreen

    { display() } loginScreen { openSignIn() } signInScreen { signIn(email, password) } homeScreen { isMapDisplayed() signOut() } }
  12. Authorization of the users DATA UI with mocking @Test fun

    shouldOpenMapScreenAfterSuccessfulSignIn() { prepare(testScope) { mockLocationProvider() mockSuccessfulSignIn(email, password) } signInScreen { signIn(email, password) } homeScreen { isSuccessfullyLoaded() } }
  13. Authorization of the user – differences End-To-End test cases UI

    tests with mocking B Interaction with server Verification interaction with a server C Fast UI verification Fast and independent on resources tests A Entry point Start tests from the main screen B No interaction with server Verification UI and interaction with mock object A Entry point Start test from any screen of the app
  14. Search notes - scenarios Test scenario #1: Display all notes

    Test scenario #2: Handle error during loading notes Test scenario #3: Display search results
  15. Display search results End To End @Test fun shouldVerifyAddingAndSearchNote() {

    val noteText = "test note ${Date().time}" splashScreen { display() } loginScreen { openSignIn() } signInScreen { signIn(email, password) } homeScreen { isMapDisplayed() openAddNoteFragment() addNote(noteText) openSearchFragment() searchNoteByText(noteText) isNoteInSearchResult(noteText) signOut() } }
  16. Display search results DATA UI with mocking @Test fun shouldSearchByNotesAndDisplayResult()

    { val expectedItemCount = testNotes.size prepare(testScope) { mockLoadingEmptyListOfNotes() mockSearchNoteByAnyText(testNotes) } homeScreen { searchNoteByText(searchInput) verifySearchResultsByItemCount(expectedItemCount) verifySearchResults(testNotes) } }
  17. Search notes – differences End-To-End test cases UI tests cases

    with mocking B Interaction with server Verification interaction with a server A Entry point Start tests from the main screen C Data from the server Depend on data from the server E Fast UI verification Fast and independent on resources tests B No interaction with server Verification UI and interaction with mock object A Entry point Start test from any screen of the app D App architecture Architecture should support mocking C UI component verification Testing only fragments, view without main Activity
  18. Q&A Espresso: https://developer.android.com/training/testing/espresso/ UiAutomator: https://developer.android.com/training/testing/ui-automator/ Appium: http://appium.io/ Android Testing codelab:

    https://codelabs.developers.google.com/codelabs/android-testing/ Instrumentation Testing Robots https://academy.realm.io/posts/kau-jake-wharton-testing-robots/ MapNotes: https://github.com/AlexZhukovich/MapNotes Blog: http://alexzh.com/ @Alex_Zhukovich