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

Where and how to run UI tests (Droidcon Lisbon ...

Where and how to run UI tests (Droidcon Lisbon & Android Makers, Paris)

UI tests have never been easy.
Yes, with frameworks like Kaspresso and Kakao it gets easier year by year.

However, running ui tests still raises a lot of questions, like:
- Which test runner do I need to use?
- How to deal with unstable network?
- Emulator or Real Device?
- How to clear the state and why?
- How to make tests less flakiness?
- How to organise good reports?

We gonna talk about all of these topics and deep dive into test runners, emulators, why adb is not the best option and find the most pragmatic options for your team no matter how big is it

Alexey Bykov

April 26, 2022
Tweet

More Decks by Alexey Bykov

Other Decks in Programming

Transcript

  1. 2 Alexey Bykov Android Software Engineer @Revolut • 5 years

    in Android • EX Programm commette: AppsConf, Podlodka • EX: Android Academy MSK @nonewsss
  2. 6 
 unit and integration tests All tested! 
 unit

    and integration tests 
 unit and integration tests 
 unit and integration tests 
 unit and integration tests
  3. 22 Mobile: Bug found 1. Fix/open PR, wait for the

    review, merge 2. Test it 3. Upload to the Google play
  4. 23 Mobile: Bug found 1. Fix/open PR, wait for the

    review, merge 2. Test it 3. Upload to the Google play 4. Wait for Google play review…
  5. 24 Mobile: Bug found 1. Fix/open PR, wait for the

    review, merge 2. Test it 3. Upload to the Google play 4. Wait for Google play review… 5. Wait for rolling out
  6. 25 Mobile: Bug found 1. Fix/open PR, wait for the

    review, merge 2. Test it 3. Upload to the Google play 4. Wait for Google play review… 5. Wait for rolling out 6. Wait for the users to download it
  7. 31

  8. 33 Writing Running AndroidJUnitRunner Orchestrator Google provided Espresso UiAutomator Google

    provided Community KAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Community Flank
  9. 34 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Community Whereto run Emulator Real device Docker container Real network Mock network Device Device condition
  10. 35 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Community Whereto run Emulator Real device Docker container Device Real network Mock network Device condition Infrastructure Cloud Own Server Firebase GCloud Amazon Kubernetes
  11. 36 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Community Whereto run Emulator Real device Docker container Device Real network Mock network Device condition Infrastructure Cloud Own Server Firebase GCloud Amazon Kubernetes Cloud Processes CI/CD Release pipeline Validation on PR When to write?
  12. 37 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Community Whereto run Emulator Real device Docker container Device Real network Mock network Device condition Infrastructure Cloud Own Server Firebase GCloud Amazon Kubernetes Cloud Processes CI/CD Release Validation on PR When to write? Analytics Test report Allure In fl ux db Grafana Found bugs dynamics
  13. 38 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Community Whereto run Emulator Real device Docker container Device Real network Mock network Device condition Infrastructure Cloud Own Server Firebase GCloud Amazon Kubernetes Cloud Processes CI/CD Release Validation on PR When to write? Analytics Test report Allure In fl ux db Grafana Found bugs dynamics
  14. 39 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Community Whereto run Emulator Real device Docker container Device Real network Mock network Device condition Infrastructure Own Server Firebase GCloud Amazon Kubernetes Processes CI/CD Release Validation on PR When to write? Analytics Test report Allure In fl ux db Grafana Found bugs dynamics Cloud Should work ne
  15. 40 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KUiAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Whereto run Emulator Real device Docker container Device Real network Mock network Device condition Infrastructure Cloud Own Server Firebase GCloud Amazon Kubernetes Cloud Processes CI/CD Release Validation on PR When to write? Analytics Test report Allure In fl ux db Grafana Found bugs dynamics This talk about
  16. 41 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KUiAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Whereto run Emulator Real device Docker container Device Real network Mock network Device condition Infrastructure Cloud Own Server Firebase GCloud Amazon Kubernetes Cloud Processes CI/CD Release Validation on PR When to write? Analytics Test report Allure In fl ux db Grafana Found bugs dynamics This talk about
  17. 42 Writing Running AndroidJUnitRunner Orchestrator Espresso UiAutomator Google provided Community

    KUiAutomator Kakao Kaspresso Spoon Marathon AvitoRunner Composer Fork Flank Google provided Whereto run Emulator Real device Docker container Device Real network Mock network Device condition Infrastructure Cloud Own Server Firebase GCloud Amazon Kubernetes Cloud Processes CI/CD Release Validation on PR When to write? Analytics Test report Allure In fl ux db Grafana Found bugs dynamics This talk about
  18. 54 adb shell am instrument -w -m -e debug false

    -e class 'com.alexbykov.myapplication.ExampleInstrumentedTest#myTest' com.alexbykov.myapplication.test/ androidx.test.runner.AndroidJUnitRunner To launch tests:
  19. 55 adb shell am instrument -w -m -e debug false

    -e class 'com.alexbykov.myapplication.ExampleInstrumentedTest#myTest' com.alexbykov.myapplication.test/ androidx.test.runner.AndroidJUnitRunner To launch tests:
  20. 58 adb shell am instrument -w -m -e debug false

    -e package 'com.alexbykov.myapplication' com.alexbykov.myapplication.test/ androidx.test.runner.AndroidJUnitRunner ./gradlew assembleDebug ./gradlew assembleDebugAndroidTest adb install debug.apk adb install instrumented.apk Full picture // install // build // run
  21. 68 Within process clearing 1. LogoutComponent from the code @Before

    fun setUp() { DI.provideLogoutInteractor().logout() }
  22. 73 Internal storage clearing All db saved: /data/data/packagename/databases/ @get:Rule val

    clearDatabasesRule = ClearDatabasesRule() @get:Rule val clearFilesRule = ClearFilesRule() @get:Rule val clearPreferencesRule = ClearPreferencesRule()
  23. 74 Internal storage clearing All db saved: /data/data/packagename/databases/ @get:Rule val

    clearDatabasesRule = ClearDatabasesRule() @get:Rule val clearFilesRule = ClearFilesRule() @get:Rule val clearPreferencesRule = ClearPreferencesRule() https://github.com/AdevintaSpain/Barista
  24. 77 It doesn’t solve all issues 1. You may have

    Runtime cache 2. App/Test process may be crashed ¯\_(ツ)_/¯
  25. 79 Within process: Conclusions - Bottleneck - Don’t have any

    guarantee about clearing - App/Test process might be crashed + Fast implementation + Fast execution
  26. 82 First thought adb shell am instrument -w -m -e

    debug false -e class ‘….ExampleInstrumentedTest#test1,….ExampleInstrumentedTest#tes t2’ com.alexbykov.myapplication.test/ androidx.test.runner.AndroidJUnitRunner
  27. 85 Launch second test adb shell am instrument …. ExampleInstrumentedTest#test1

    adb shell pm clear … adb shell am instrument …. ExampleInstrumentedTest#test2
  28. 86 Clear again adb shell am instrument …. ExampleInstrumentedTest#test1 adb

    shell pm clear … adb shell am instrument …. ExampleInstrumentedTest#test2 adb shell pm clear …
  29. 99 Package clear: Conclusions - It takes time - It’s

    complicated + Isolated environment
  30. 101 It’s complicated 2 more apk you need to install

    orchestrator.apk services.apk Mobile device less reliable than host PC
  31. 105 1. Reason of akiness Production code // toasts, custom

    views, http-requests, databases, animations
  32. 124 Example1: Kaspresso fun <T> invokeFlakySafely( params: FlakySafetyParams, failureMessage: String?

    = null, action: () -> T ): T { var cachedError: Throwable val startTime = System.currentTimeMillis() do { try { return action.invoke() } catch (error: Throwable) { if (error.isAllowed(params.allowedExceptions)) { cachedError = error lock.withLock { Timer().schedule(params.intervalMs) { lock.withLock { condition.signalAll() } } condition.await() } } else { throw error } } } while (System.currentTimeMillis() - startTime <= params.timeoutMs) throw cachedError.withMessage(failureMessage) }
  33. 125 fun <T> invokeFlakySafely( params: FlakySafetyParams, failureMessage: String? = null,

    action: () -> T ): T { var cachedError: Throwable val startTime = System.currentTimeMillis() do { try { return action.invoke() } catch (error: Throwable) { … } } while (System.currentTimeMillis() - startTime <= params.timeoutMs) throw cachedError.withMessage(failureMessage) } Trying execute actions
  34. 126 fun <T> invokeFlakySafely( params: FlakySafetyParams, failureMessage: String? = null,

    action: () -> T ): T { var cachedError: Throwable val startTime = System.currentTimeMillis() do { try { return action.invoke() } catch (error: Throwable) { … } } while (System.currentTimeMillis() - startTime <= params.timeoutMs) throw cachedError.withMessage(failureMessage) } In a loop
  35. 128 Example2: Noti cation class CloseNotificationsRule : ExternalResource() { override

    fun before() { UiDevice .getInstance(InstrumentationRegistry.getInstrumentation()) .pressHome() } }
  36. 129 Example2: Noti cation class CloseNotificationsRule : ExternalResource() { override

    fun before() { UiDevice .getInstance(InstrumentationRegistry.getInstrumentation()) .pressHome() } } @get:Rule val closeNotification = CloseNotificationsRule()
  37. 132 Example: Disabling animations adb shell "settings put global window_animation_scale

    0.0" adb shell "settings put global transition_animation_scale 0.0" adb shell "settings put global animator_duration_scale 0.0"
  38. 133 Example: Other adb shell "settings put global window_animation_scale 0.0"

    adb shell "settings put global transition_animation_scale 0.0" adb shell "settings put global animator_duration_scale 0.0" adb shell "settings put system screen_off_timeout 2147483647" adb shell "settings put secure long_press_timeout 1500"
  39. 139 Marathon vision Just connect Devices to adb, I’ll do

    the job https://github.com/MarathonLabs/marathon
  40. 147 Idea: Flakiness strategy — Calculate probability of passing —

    Launch flakiness test in parallel before it’s failed
  41. 149 Flakiness strategy Test1: Flaky in 50% //0.5 — probability

    of passing We want minimum: 80% //0.8 — probability of passing
  42. 150 Flakiness strategy Test1: Flaky in 50% //0.5 — probability

    of passing We want minimum: 80% //0.8 — probability of passing Max parallel run: 3 //because more can affect speed of run
  43. 151 Probability 0.5 = 0.125 Test1: Flaky in 50% We

    want minimum: 80% Max parallel run: 3 //probability of all failed 3
  44. 152 Probability 0.5 = 0.125 1 - 0.125 = 0.875

    > 0.8 Test1: Flaky in 50% We want minimum: 80% Max parallel run: 3 //probability of all failed //Test will be executed 3 times in parallel 3
  45. 157

  46. 160 But we can do that via Allure allure generate

    report marathon_output/allure-results -o marathon_output/ allure-report --clean
  47. 163 Marathon + Retries + Flakiness strategy + Good report

    and allure support + Isolated environment applicationPmClear: true testApplicationPmClear: true
  48. 164 Marathon + Retries + Flakiness strategy + Good report

    and allure support + Isolated environment + ADB replacement
  49. 169

  50. 172 Adam vs ADB ADB: 300 tests T: 32 min

    I/O: 22% CPU: 48% Adam: 300 tests T: 26 min I/O: 19% CPU: 47 %
  51. 173 Marathon + Retries + Flakiness strategy + Good report

    and allure support + Isolated environment + ADB replacement
  52. 174 Marathon + Retries + Flakiness strategy + Good report

    and allure support + Isolated environment + ADB replacement + Flakiness validation strictMode: true shardingStrategy: type: "count" count: 100
  53. 175 Marathon + Retries + Flakiness strategy + Good report

    and allure support + Isolated environment + ADB replacement + Flakiness validation + Cross platform (iOS support)
  54. 178 Avito runner vision Just connect Kubernetes to me, I’ll

    do the job https://avito-tech.github.io/avito-android/test_runner/TestRunner/
  55. 187 Difference Marathon Avito Test Runner - Auto-scalling - Adam

    - Flakiness strategy - CLI - Doesn’t support iOS
  56. 188 Difference Marathon Avito Test Runner - Auto-scalling - Adam

    - Flakiness strategy - CLI - Doesn’t support iOS - It’s not ready for open source
  57. 191 Flank vision https://flank.github.io/flank/ I’ll make your life easier to

    work with Firebase Test Lab https://firebase.google.com/products/test-lab
  58. 192 Flank + Don’t need to care about where to

    run — Firebase Test Lab — paid service + Crossplatform
  59. 195 It depends :) Product? Team size? Company? Scalability? How

    often do you need to run? How many tests do you have? How often your codebase changed?
  60. 196 That’s on you Marathon, Avito Runner: + invest your

    time to build infrastructure + invest your time to support
  61. 197 That’s on you Marathon, Avito Runner: + invest your

    time to support + invest your time to build infrastructure Flank + Firebase test lab: + You have to pay always
  62. 204 Real device + Real condition + Doesn’t consume CI

    resources — Room with special condition
  63. 205 Real device + Real condition + Doesn’t consume CI

    resources — Room with special condition — Breaks often
  64. 215 Why Fresh emulator instance per each test suit Only

    your application installed Special configuration
  65. 216 PlayStore.enabled=false abi.type=x86_64 avd.ini.encoding=UTF-8 hw.cpu.arch=x86_64 hw.cpu.ncore=2 hw.ramSize=2048 hw.lcd.density=120 hw.lcd.width=320 hw.lcd.height=480

    hw.audioInput=no hw.audioOutput=no hw.accelerometer=no hw.gyroscope=no hw.dPad=no hw.mainKeys=yes hw.keyboard=no hw.sensors.proximity=no hw.sensors.magnetic_field=no hw.sensors.orientation=no hw.sensors.temperature=no hw.sensors.light=no hw.sensors.pressure=no hw.sensors.humidity=no hw.sensors.magnetic_field_uncalibrated=no hw.sensors.gyroscope_uncalibrated=no image.sysdir.1=system-images/android-29/google_apis/x86_64/ tag.display=Google APIs tag.id=google_apis skin.dynamic=yes skin.name=320x480 disk.dataPartition.size=8G Special configuration
  66. 217 PlayStore.enabled=false abi.type=x86_64 avd.ini.encoding=UTF-8 hw.cpu.arch=x86_64 hw.cpu.ncore=2 hw.ramSize=2048 hw.lcd.density=120 hw.lcd.width=320 hw.lcd.height=480

    hw.audioInput=no hw.audioOutput=no hw.accelerometer=no hw.gyroscope=no hw.dPad=no hw.mainKeys=yes hw.keyboard=no hw.sensors.proximity=no hw.sensors.magnetic_field=no hw.sensors.orientation=no hw.sensors.temperature=no hw.sensors.light=no hw.sensors.pressure=no hw.sensors.humidity=no hw.sensors.magnetic_field_uncalibrated=no hw.sensors.gyroscope_uncalibrated=no image.sysdir.1=system-images/android-29/google_apis/x86_64/ tag.display=Google APIs tag.id=google_apis skin.dynamic=yes skin.name=320x480 disk.dataPartition.size=8G Special configuration
  67. 218 PlayStore.enabled=false abi.type=x86_64 avd.ini.encoding=UTF-8 hw.cpu.arch=x86_64 hw.cpu.ncore=2 hw.ramSize=2048 hw.lcd.density=120 hw.lcd.width=320 hw.lcd.height=480

    hw.audioInput=no hw.audioOutput=no hw.accelerometer=no hw.gyroscope=no hw.dPad=no hw.mainKeys=yes hw.keyboard=no hw.sensors.proximity=no hw.sensors.magnetic_field=no hw.sensors.orientation=no hw.sensors.temperature=no hw.sensors.light=no hw.sensors.pressure=no hw.sensors.humidity=no Special configuration
  68. 221 Emulator Emulator + Easy manageable + Easy configurable +

    Work faster — Consume CI resources — It’s not a real device
  69. 227 docker run -d -p 5555:5555 -p 5554:5554 -p 8554:8554

    
 --privileged avitotech/android-emulator-29:915c1f20be //Run emulator (headless instance)
  70. 228 docker run -d -p 5555:5555 -p 5554:5554 -p 8554:8554

    
 --privileged avitotech/android-emulator-29:915c1f20be //Run emulator (headless instance) //Connect to adb adb connect localhost:5555
  71. 229 docker run -d -p 5555:5555 -p 5554:5554 -p 8554:8554

    
 --privileged avitotech/android-emulator-29:915c1f20be //Run emulator (headless instance) //Connect to adb adb connect localhost:5555 //Kill emulators (after tests finished) docker kill $(docker ps -q)
 docker rm $(docker ps -a -q)
  72. 231 Conclusion Headless emulators — pragmatic choice (faster and easy)

    It’s fine to start with real device (If your build agent is not powerful)
  73. 236 Do nothing (Real network) + E2E testing — Server

    is down (applies to development) — Network issue
  74. 242 Dedicated stage + A bit stablier — Network issue

    — Not as same as a real server — It takes time to support
  75. 247 Mock it / Stub it + Stable + Still

    can catch the bugs + Pragmatic
  76. 248 Mock it / Stub it + Stable + Still

    can catch the bugs — Not fully E2E + Pragmatic — Requires some effort from engineers
  77. 253 Network: How to mock/stub? MockWebServer Custom Interceptor for testing

    DI: Dagger2/Hilt, Toothpick, Koin/Kodein Service Locator / different src
  78. 257 Why: — A lot of requests //100+ in some

    features — Long hours to mock
  79. 265 - recorded: 2021-10-13T14:30:23.507Z request: method: GET uri: https://api.server.com/inbox headers:

    response: status: 200 headers: date: Wed, 13 Oct 2021 14:30:26 GMT request-id: 9JAJH8Y9JMRU server: MyServer body: '{"messages":[]}' Example
  80. 276 Solutions Wiremock + MITM under the hood + Requests

    indexing — Support only 1 host — Not Android Friendly
  81. 281 Solutions Wiremock OkReplay + Readable report + Interceptor-based solution

    — Lack of indexing — Constant execution time per each request
  82. 282 Solutions Wiremock OkReplay + Readable report + Interceptor-based solution

    — Lack of indexing — Constant execution time per each request — Abandoned :(
  83. 285 Network: Conclusion — Mocked/Stubbed network it — the only

    way to have stable UI tests — Small project: MockWebServer
  84. 286 Network: Conclusion — Mocked/Stubbed network it — the only

    way to have stable UI tests — Small project: MockWebServer — Have a lot of requests to mock? — OkReplay
  85. 287 Network: Conclusion — Mocked/Stubbed network it — the only

    way to have stable UI tests — Small project: MockWebServer — Have a lot of requests to mock? — OkReplay — Didn’t use OkHttp? — Wiremock
  86. 291 Before you get started Do not even start if

    you want to try it just for fun
  87. 292 Before you get started Do not even start if

    you want to try it just for fun You should have clear goal
  88. 293 Before you get started You should have clear goal

    Which solves real problems Do not even start if you want to try it just for fun
  89. 295 Thanks to Eugene Matsyuk Alexey Tvorogov Dmitriy Voronin Anton

    Malinskiy Dmitry Movchan Kaspresso adb server Kaspresso creator Marathon creator Avito runner creator Kaspresso adb server & a lot of researches
  90. 296 Resources 1. https://github.com/KasperskyLab/Kaspresso 3. https://github.com/MarathonLabs/marathon 2. https://proandroiddev.com/autotests-on-android-the-entire-picture-51c07995fc2f ( 4.

    https://github.com/avito-tech/avito-android 5. https://github.com/Flank/flank 6. https://github.com/airbnb/okreplay 7. https://github.com/wiremock/wiremock