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. 1.
  2. 5.

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

    Require Android device (instrumentation tests) Tests
  3. 12.
  4. 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")))) } }
  5. 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")))) } }
  6. 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")))) } }
  7. 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")))) } }
  8. 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")))) } }
  9. 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
  10. 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
  11. 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] } }
  12. 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 } } }
  13. 44.

    RecyclerViewActions ü actionOnHolderItem ü actionOnItem ü actionOnItemAtPosition ü scrollTo ü

    scrollToHolder ü scrollToPosition onView(withId(R.id.recyclerView)) .perform(actionOnItemAtPosition(3, click()));
  14. 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
  15. 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) }
  16. 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() } }
  17. 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) }
  18. 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
  19. 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​
  20. 51.

    UI AutomatorViewer Android Studio Tools / Android / Android Device

    Monitor Application {ANDROID_SDK} / tools / uiautomatorviewer
  21. 53.

    Basic UI Automator components ü UiDevice ü UiCollection ü UiObject

    / UiObject2 ü UiScrollable ü UiSelector ü Configurator
  22. 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()
  23. 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() } }
  24. 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() } }
  25. 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() } }
  26. 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() } }
  27. 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() } }
  28. 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() } }
  29. 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() } }
  30. 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() } }
  31. 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()) ... } }
  32. 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()) ... } }
  33. 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/