Make UI Testing Simple with a Hot Sip of Kakao

Make UI Testing Simple with a Hot Sip of Kakao

Android UI Testing uses Espresso library to match and validate the views inside the app. Even though it works really good, sometimes Espresso API can be very verbose and hard to understand. In this talk I introduced how you can make use of Kakao, an open source library that provides DSL for Espresso written in Kotlin, to simplify your UI Testing.

6338c8fa4e2e6325094fe30b1e9f9443?s=128

Malvin Sutanto

May 19, 2019
Tweet

Transcript

  1. 5.

    Espresso 1. @Test 2. fun test_WhenFabIsPressed_ThenTextIsChanged() { 3. val text

    = onView(withId(R.id.text)) 4. text.check(matches(withText("Hello World!"))) 5. onView(withId(R.id.text_input)).perform(ViewActions.typeText("My new string")) 6. onView(withId(R.id.fab)).perform(ViewActions.click()) 7. text.check(matches(withText("My new string"))) 8. }
  2. 6.

    Espresso • Verbose ◦ Lots of boilerplate codes ◦ Cognitive

    burden • Complex view layouts ◦ Recycler views... • Readability ◦ View visibility, descendants, etc • Difficult to maintain
  3. 7.

    Espresso 1. @Test 2. fun test_WhenFabIsPressed_ThenTextIsChanged() { 3. val text

    = onView(withId(R.id.text)) 4. text.check(matches(withText("Hello World!"))) 5. onView(withId(R.id.text_input)).perform(ViewActions.typeText("My new string")) 6. onView(withId(R.id.fab)).perform(ViewActions.click()) 7. text.check(matches(withText("My new string"))) 8. }
  4. 8.

    Espresso 1. @Test 2. fun test_WhenFabIsPressed_ThenTextIsChanged() { 3. )) 4.

    text.check(matches(withText("Hello World!"))) 5. onView(withId(R.id.text_input)).perform(ViewActions.typeText("My new string")) 6. onView(withId(R.id.fab)).perform(ViewActions.click()) 7. text.check(matches(withText("My new string"))) 8. }
  5. 9.

    Espresso 1. @Test 2. fun test_WhenFabIsPressed_ThenTextIsChanged() { 3. 4. text

    matchesText("Hello World!"))) 5. text_input typeText("My new string")) 6. fab click()) 7. text.matchesText("My new string"))) 8. }
  6. 10.
  7. 12.

    Page Object Pattern • Page: ◦ A collection of logical

    UI elements. • Has references to: ◦ How to get hold of views. ◦ Functions to manipulate that views. • In Android ◦ Can represent an Activity or a Fragment. ◦ Or can be smaller... *https://martinfowler.com/bliki/PageObject.html
  8. 13.

    Kakao https://github.com/agoda-com/Kakao • DSL for Espresso in Kotlin • Implements

    Page Object pattern ◦ Screen • Increase readability and reusability • Extendability • Nice and simple androidTestImplementation 'com.agoda.kakao:kakao:2.0.0'
  9. 14.

    Screen 1. class FormScreen : Screen<FormScreen>() { 2. val phone

    = KView { withId(R.id.phone) } 3. val email = KEditText { 4. withId(R.id.email) 5. withText(R.string.email) 6. } 7. }
  10. 15.

    KViews 1. class FormScreen : Screen<FormScreen>() { 2. val phone

    = KView { withId(R.id.phone) } 3. val email = KEditText { 4. withId(R.id.email) 5. withText(R.string.email) 6. } 7. }
  11. 16.

    KViews 1. class FormScreen : Screen<FormScreen>() { 2. val phone

    = KView { withId(R.id.phone) } 3. val email = KEditText { 4. withId(R.id.email) 5. withText(R.string.email) 6. } 7. }
  12. 18.

    Screen 1. class AppScreen : Screen<AppScreen>() { 2. val text

    = KTextView { withId(R.id.text) } 3. val textInput = KEditText { withId(R.id.text_input) } 4. val fab = KButton { withId(R.id.fab) } 5. }
  13. 19.

    Screen 1. class AppScreen : Screen<AppScreen>() { 2. val text

    = KTextView { withId(R.id.text) } 3. val textInput = KEditText { withId(R.id.text_input) } 4. val fab = KButton { withId(R.id.fab) } 5. }
  14. 20.

    Screen 1. class AppScreen : Screen<AppScreen>() { 2. val text

    = KTextView { withId(R.id.text) } 3. val textInput = KEditText { withId(R.id.text_input) } 4. val fab = KButton { withId(R.id.fab) } 5. }
  15. 21.

    Screen 1. class AppScreen : Screen<AppScreen>() { 2. val text

    = KTextView { withId(R.id.text) } 3. val textInput = KEditText { withId(R.id.text_input) } 4. val fab = KButton { withId(R.id.fab) } 5. }
  16. 22.

    Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen<AppScreen> {

    4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }
  17. 23.

    Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen<AppScreen> {

    4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }
  18. 24.

    Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen<AppScreen> {

    4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }
  19. 25.

    Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen<AppScreen> {

    4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }
  20. 26.

    Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen<AppScreen> {

    4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }
  21. 27.

    Let’s Compare... 1. @Test 2. fun test_WhenFabIsPressed_ThenTextIsChanged() { 3. val

    text = onView(withId(R.id.text)) 4. text.check(matches(withText("Hello World!"))) 5. onView(withId(R.id.text_input)).perform(ViewActions.typeText("My new string")) 6. onView(withId(R.id.fab)).perform(ViewActions.click()) 7. text.check(matches(withText("My new string"))) 8. } 9. 10. @Test 11. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 12. onScreen<AppScreen> { 13. text { hasText("Hello World!") } 14. textInput { typeText("My new string") } 15. fab { click() } 16. text { hasText("My new string") } 17. } 18. }
  22. 28.

    Let’s Compare... 1. @Test 2. fun test_WhenFabIsPressed_ThenTextIsChanged() { 3. 4.

    text matchesText("Hello World!"))) 5. text_input typeText("My new string")) 6. fab click()) 7. text.matchesText("My new string"))) 8. } 9. 10. @Test 11. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 12. onScreen<AppScreen> { 13. text { hasText("Hello World!") } 14. textInput { typeText("My new string") } 15. fab { click() } 16. text { hasText("My new string") } 17. } 18. }
  23. 30.

    RecyclerView 1. class Item(parent: Matcher<View>) : KRecyclerItem<Item>(parent) { 2. val

    title: KTextView = KTextView(parent) { withId(R.id.title) } 3. val subtitle: KTextView = KTextView(parent) { withId(R.id.subtitle) } 4. } 1. val recycler: KRecyclerView = KRecyclerView({ 2. withId(R.id.recycler_view) 3. }, itemTypeBuilder = { 4. itemType(::Item) 5. }) 1. onScreen<RecyclerScreen> { 2. recycler { 3. firstChild<TestRecyclerScreen.Item> { 4. isVisible() 5. title { hasText("Title 1") } 6. } 7. } 8. }
  24. 31.

    Provide Your Own Matcher 1. val textInput = KEditText {

    2. withId(R.id.text_input) 3. withMatcher(hasImeAction(EditorInfo.IME_ACTION_SEND)) 4. } 1. /** 2. * Returns a matcher that matches views that support input methods (e.g. EditText) and have the 3. * specified IME action set in its {@link EditorInfo}. 4. * 5. * @param imeAction the IME action to match 6. */ 7. public static Matcher<View> hasImeAction(int imeAction) { 8. return hasImeAction(is(imeAction)); 9. }
  25. 32.
  26. 33.

    Kakao • Simple and easy to understand UI tests •

    Make use of Espresso API ◦ Reuse your existing matchers and assertions! • Less code to write, more tests!
  27. 34.