Slide 1

Slide 1 text

Make UI Testing Simple With a Hot Sip of Kakao

Slide 2

Slide 2 text

Malvin Sutanto Software Engineer @Wantedly Visit Android & iOS Twitter/ Medium: @malvinsutanto

Slide 3

Slide 3 text

Android UI Testing

Slide 4

Slide 4 text

Consider This Simple Application

Slide 5

Slide 5 text

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. }

Slide 6

Slide 6 text

Espresso ● Verbose ○ Lots of boilerplate codes ○ Cognitive burden ● Complex view layouts ○ Recycler views... ● Readability ○ View visibility, descendants, etc ● Difficult to maintain

Slide 7

Slide 7 text

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. }

Slide 8

Slide 8 text

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. }

Slide 9

Slide 9 text

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. }

Slide 10

Slide 10 text

Kakao

Slide 11

Slide 11 text

Page Object Pattern

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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'

Slide 14

Slide 14 text

Screen 1. class FormScreen : Screen() { 2. val phone = KView { withId(R.id.phone) } 3. val email = KEditText { 4. withId(R.id.email) 5. withText(R.string.email) 6. } 7. }

Slide 15

Slide 15 text

KViews 1. class FormScreen : Screen() { 2. val phone = KView { withId(R.id.phone) } 3. val email = KEditText { 4. withId(R.id.email) 5. withText(R.string.email) 6. } 7. }

Slide 16

Slide 16 text

KViews 1. class FormScreen : Screen() { 2. val phone = KView { withId(R.id.phone) } 3. val email = KEditText { 4. withId(R.id.email) 5. withText(R.string.email) 6. } 7. }

Slide 17

Slide 17 text

Our Simple App

Slide 18

Slide 18 text

Screen 1. class AppScreen : Screen() { 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. }

Slide 19

Slide 19 text

Screen 1. class AppScreen : Screen() { 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. }

Slide 20

Slide 20 text

Screen 1. class AppScreen : Screen() { 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. }

Slide 21

Slide 21 text

Screen 1. class AppScreen : Screen() { 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. }

Slide 22

Slide 22 text

Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen { 4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }

Slide 23

Slide 23 text

Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen { 4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }

Slide 24

Slide 24 text

Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen { 4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }

Slide 25

Slide 25 text

Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen { 4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }

Slide 26

Slide 26 text

Kakao 1. @Test 2. fun test_Kakao_WhenFabIsPressed_ThenTextIsChanged() { 3. onScreen { 4. text { hasText("Hello World!") } 5. textInput { typeText("My new string") } 6. fab { click() } 7. text { hasText("My new string") } 8. } 9. }

Slide 27

Slide 27 text

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 { 13. text { hasText("Hello World!") } 14. textInput { typeText("My new string") } 15. fab { click() } 16. text { hasText("My new string") } 17. } 18. }

Slide 28

Slide 28 text

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 { 13. text { hasText("Hello World!") } 14. textInput { typeText("My new string") } 15. fab { click() } 16. text { hasText("My new string") } 17. } 18. }

Slide 29

Slide 29 text

More Examples

Slide 30

Slide 30 text

RecyclerView 1. class Item(parent: Matcher) : KRecyclerItem(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 { 2. recycler { 3. firstChild { 4. isVisible() 5. title { hasText("Title 1") } 6. } 7. } 8. }

Slide 31

Slide 31 text

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 hasImeAction(int imeAction) { 8. return hasImeAction(is(imeAction)); 9. }

Slide 32

Slide 32 text

Summary

Slide 33

Slide 33 text

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!

Slide 34

Slide 34 text

Thank You