Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Avatar for Alex Zhukovich

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