Drink Espresso During Android Testing - Introduction

Drink Espresso During Android Testing - Introduction

Mobile apps are growing. They become more complex and require more testing. It means that it is time to integrate automating tests to your project. In this talk we will discuss shortly testing of Android project in general and User Interface testing with Espresso framework in detail.

2b0404a5db1a74f01bf3bf94d142e28c?s=128

Alex Zhukovich

September 18, 2017
Tweet

Transcript

  1. None
  2. Welcome to Android World!

  3. Application is growing

  4. Application is growing

  5. Categories of Android tests Run on the JVM (local tests)

    Require Android device (instrumentation tests) Tests
  6. How to test the project?

  7. Unit tests JUnit Hamcrest Mockito Robolectric

  8. Integration tests JUnit Hamcrest Mockito Instrumentation API

  9. Acceptance tests Espresso UiAutomator Robotium Calabash Appium

  10. Android Testing Support Library AndroidJUnitRunner JUnit4 Rules Espresso UiAutomator

  11. AndroidJUnitRunner ü JUnit4 support ü Instrumentation Registry ü Activity and

    Application life-cycle monitoring
  12. JUnit4 Rules ü ActivityTestRule ü IntentsTestRule ü ServiceTestRule ü ProviderTestRule

    ü GrantPermissionRule (API 23 – Android Marshmallow)
  13. Espresso Android 2.2 (API 8) Turn off the animations

  14. Espresso onView(ViewMatcher) .perform(ViewAction) .check(ViewAssertion)

  15. Get View by text Matcher Action Assertion onView(withText("Pay"))

  16. Get View by text Matcher Action Assertion onView(withText(R.string.pay))

  17. Get View by id Matcher Action Assertion onView(withId(R.id.pay))

  18. Click to view Matcher Action Assertion onView(withId(R.id.pay)) .perform(click())

  19. Long click to view Matcher Action Assertion onView(withId(R.id.pay)) .perform(longClick())

  20. Double click to view Matcher Action Assertion onView(withId(R.id.pay)) .perform(doubleClick())

  21. Enter a text Matcher Action Assertion onView(withId(R.id.payment)) .perform(typeText("120$"))

  22. Close a keyboard Matcher Action Assertion onView(withId(R.id.payment)) .perform(closeSoftKeyboard())

  23. Enter a text, close a keyboard Matcher Action Assertion onView(withId(R.id.payment))

    .perform(typeText("120$"), closeSoftKeyboard())
  24. Check the hint Matcher Action Assertion onView(withId(R.id.payment)) .check(matches( withHint(R.string.payment_hint) ))

  25. Check the text Matcher Action Assertion onView(withId(R.id.payment)) .check(matches( withText(R.string.payment)))

  26. Check the visibility Matcher Action Assertion onView(withId(R.id.payment)) .check(matches( isDisplayed()))

  27. onView(withId(R.id.payment)) .perform(typeText("120$"), closeSoftKeyboard()) onView(withId(R.id.pay_button)) .check(matches(isDisplayed())) .perform(click())

  28. onView(withId(R.id.payment)) .perform(typeText("120$"), closeSoftKeyboard()) onView(withId(R.id.pay_button)) .check(matches(isDisplayed())) .perform(click())

  29. Hamcrest matchers

  30. onView(withId(R.id.payment)) .check(matches( `is` withText(startsWith("Go")))) endsWith

  31. @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java)

    @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }
  32. @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule<LoginActivity>

    (LoginActivity::class.java) @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }
  33. @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java)

    @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }
  34. @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java)

    @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }
  35. @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java)

    @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches( withError(`is`("Please enter a password")))) } }
  36. @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Rule @JvmField val mActivityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java)

    @Test fun shouldVerifyEmptyPassword() { onView(withId(R.id.edit_text_username)) .perform(typeText("Alex"), closeSoftKeyboard()) onView(withId(R.id.login_button)) .perform(click()) onView(withId(R.id.password_text_layout)) .check(matches(withError(`is`( "Please enter a password")))) } } * withError – a custom matcher
  37. onView(withId(R.id.download)) .perform(click()) onView(withId(android.support.design.R.id.snackbar_text)) .check(matches( withText(R.string.downloading_movie_info)))

  38. What is an AdapterView and how it works? java.lang.Object ↳

    android.view.View ↳ android.view.ViewGroup ↳ android.widget.AdapterView <T extends android.widget.Adapter> GridView ListView Spinner
  39. Espresso onData(ObjectMatcher) .DataOptions .perform(ViewAction) .check(ViewAssertion)

  40. onData – ArrayAdapter onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))) .check(matches(isDisplayed())) val arrayAdapter = ArrayAdapter<String>(

    this@DetailActivity, android.R.layout.simple_list_item_1, android.R.id.text1, coffeeList)
  41. onData – custom adapter @Test fun clickCoffeeItem() { ... onData(withCoffee(espresso))

    .inAdapterView(withId(R.id.detail_list)) .perform(click()); } class CoffeeAdapter : BaseAdapter() { ... override fun getItem(position: Int): Any { return mOrderedCoffee.keys.toTypedArray()[position] } }
  42. fun withCoffee(expected: Coffee) : Matcher<Any> { return object : BoundedMatcher<Any,

    Coffee>(Coffee::class.java) { override fun describeTo(description: Description) { description.appendText("has value: $expected") } override fun matchesSafely(item: Coffee): Boolean { return item == expected } } }
  43. RecyclerView java.lang.Object ↳ android.view.View ↳ android.view.ViewGroup ↳ android.support.v7.widget.RecyclerView It is

    not an AdapterView
  44. RecyclerViewActions ü actionOnHolderItem ü actionOnItem ü actionOnItemAtPosition ü scrollTo ü

    scrollToHolder ü scrollToPosition onView(withId(R.id.recyclerView)) .perform(actionOnItemAtPosition(3, click()));
  45. IdlingResource class ServiceIdlingResource : IdlingResource { lateinit var mResourceCallback :

    ResourceCallback override fun getName(): String { return ServiceIdlingResource::class.simpleName!! } override fun isIdleNow(): Boolean { val isIdle = !isIntentServiceRunning() if (isIdle) { mResourceCallback.onTransitionToIdle() } return isIdle } override fun registerIdleTransitionCallback(callback: ResourceCallback) { mResourceCallback = callback } fun isIntentServiceRunning() : Boolean { ... } } http://blog.sqisland.com/2015/04/espresso-custom-idling-resource.html
  46. Using IdlingResource @Before fun setUp() { mLoadingIdlingResource = ServiceIdlingResource() IdlingRegistry.getInstance().register(mLoadingIdlingResource)

    } @Test fun shouldVerifyMessageDuringDownloadingMoviesInfo() { onView(withId(R.id.fab)) .perform(click()) onView(withId(android.support.design.R.id.snackbar_text)) .check(matches(withText(R.string.downloading_movie_info))) } @After fun tearDown() { IdlingRegistry.getInstance().unregister(mLoadingIdlingResource) }
  47. CountingIdlingResource class MovieDetailActivity : AppCompatActivity() { val mCountingIdlingResource = CountingIdlingResource(LOADING_MOVIE_DETAILS)

    companion object { private const val LOADING_MOVIE_DETAILS = "loading_movie_details" } fun loadMore() { ... mCountingIdlingResource.increment() } fun showMovies() { ... mCountingIdlingResource.decrement() } }
  48. Using CountingIdlingResource @Before fun setUp() { mLoadingIdlingResource = mMovieDetailActivityRule. activity.mCountingIdlingResource

    IdlingRegistry.getInstance().register(mLoadingIdlingResource) } @Test fun shouldVerifyMessageDuringDownloadingMoviesInfo() { onView(withId(R.id.fab)) .perform(click()) onView(withId(android.support.design.R.id.snackbar_text)) .check(matches(withText(R.string.downloading_movie_info))) } @After fun tearDown() { IdlingRegistry.getInstance().unregister(mLoadingIdlingResource) }
  49. Configure a custom intent @Rule @JvmField val mMovieDetailActivityRule = ActivityTestRule<MovieDetailActivity>(

    MovieDetailActivity::class.java, true, false) // is activity launch? @Test fun `Should display message during downloading movie info`() { val context = InstrumentationRegistry.getTargetContext() val movieIntent = Intent(context, MovieDetailActivity::class.java) movieIntent.putExtra("movie_id", ANNABELLE_MOVIE) mMovieDetailActivityRule.launchActivity(movieIntent) ... } App data: https://www.themoviedb.org
  50. Handle animation inside the app val animatorSpeed = Settings.Global.getFloat(​ contentResolver,​

    SETTINGS_NAME,​ 0.0f)​ ​ ü Settings.Global.ANIMATOR_DURATION_SCALE ​ ü Settings.Global.TRANSITION_ANIMATION_SCALE​ ü Settings.Global.WINDOW_ANIMATION_SCALE​
  51. UI AutomatorViewer Android Studio Tools / Android / Android Device

    Monitor Application {ANDROID_SDK} / tools / uiautomatorviewer
  52. UiAutomator Android 4.3 (API 18) Interacting with any application Interacting

    with a system
  53. Basic UI Automator components ü UiDevice ü UiCollection ü UiObject

    / UiObject2 ü UiScrollable ü UiSelector ü Configurator
  54. UiDevice ü Press "Home" button mUiDevice.pressHome() ü Press "Back" button

    mUiDevice.pressBack() ü Press “Power” button mUiDevice.wakeUp() ü Click at arbitrary coordinates mUiDevice.click(300, 215) ü Open the notifications mUiDevice.openNotification()
  55. Test of calculator application class CalculatorAppTest { lateinit var mDevice:

    UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }
  56. Test of calculator application class CalculatorAppTest { lateinit var mDevice:

    UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }
  57. Test of calculator application class CalculatorAppTest { lateinit var mDevice:

    UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }
  58. Test of calculator application class CalculatorAppTest { lateinit var mDevice:

    UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }
  59. Test of calculator application class CalculatorAppTest { lateinit var mDevice:

    UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }
  60. Test of calculator application class CalculatorAppTest { lateinit var mDevice:

    UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }
  61. Test of calculator application class CalculatorAppTest { lateinit var mDevice:

    UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }
  62. Test of calculator application class CalculatorAppTest { lateinit var mDevice:

    UiDevice @Before fun setUp() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressHome() mDevice.findObject(By.desc("Apps list")).click() mDevice.findObject(By.desc("Calculator")).click() } @Test fun shouldSumThreeAndFive() { mDevice.wait(Until.hasObject(By.text("5")), 3_000L) val five = mDevice.findObject(By.text("5")) val plus = mDevice.findObject(By.text("+")) val three = mDevice.findObject(By.text("3")) val equal = mDevice.findObject(By.text("=")) five.click() plus.click() three.click() equal.click() val result = mDevice.findObject(By.res("com.android.calculator2", "result")) assertEquals("8", result.text) } @After fun tearDown() { mDevice.pressBack() } }
  63. Espresso + UiAutomator Interacting with a system Better interaction with

    the app
  64. @RunWith(AndroidJUnit4::class.java) class FullOrderTest { ... @Test fun shouldOrderThreeEspressos() { ...

    onView(withId(R.id.pay)).perform(click()) mDevice.openNotification() mDevice.wait(Until.hasObject( By.text(NOTIFICATION_TITLE)), TIMEOUT) val title = mDevice.findObject( By.text(NOTIFICATION_TITLE)) assertEquals(NOTIFICATION_TITLE, title.getText()) ... } }
  65. @RunWith(AndroidJUnit4::class.java) class FullOrderTest { ... @Test fun shouldOrderThreeEspressos() { ...

    onView(withId(R.id.pay)).perform(click()) mDevice.openNotification() mDevice.wait(Until.hasObject( By.text(NOTIFICATION_TITLE)), TIMEOUT) val title = mDevice.findObject( By.text(NOTIFICATION_TITLE)) assertEquals(NOTIFICATION_TITLE, title.getText()) ... } }
  66. Q & A 1. Testing Support library https://developer.android.com/topic/libraries/testing-support-library/ 2. Android

    Testing Codelab https://codelabs.developers.google.com/codelabs/android-testing/ 3. Espresso https://developer.android.com/training/testing/espresso/ 4. UiAutomator https://developer.android.com/training/testing/ui-automator.html 5. Blog http://alexzh.com/