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

Building Android Testing Infrastructure

Mohit S
August 02, 2022

Building Android Testing Infrastructure

Mohit S

August 02, 2022
Tweet

More Decks by Mohit S

Other Decks in Programming

Transcript

  1. Mohit Sarveiya
    Building Android Testing Infrastructure

    @heyitsmohit

    View Slide

  2. Building Android Testing Infrastructure
    ● Automation Tools

    ● Building Infra pipelines

    ● Snapshot Testing

    ● Gradle Testing

    View Slide

  3. Challenges
    Teams
    Monorepo

    View Slide

  4. Arch Fragmentation
    Codebase
    Legacy code
    Feature A
    Feature B

    View Slide

  5. Testing
    Codebase 50% Code coverage
    20% Code coverage

    View Slide

  6. Test Coverage
    Gradle Builds
    Database
    UI
    API

    View Slide

  7. Testing Infra Goals
    ● Automate

    ● Scale

    ● Testing Gradle, Database, UI

    View Slide

  8. UI Test Automation

    View Slide

  9. Test Case
    Test Suite
    Test Case Test Case Test Case

    View Slide

  10. Test Case
    Test Case
    Test Case
    Test Case

    View Slide

  11. Test Case
    Test Case
    Test Case
    Test Case
    Firebase Test Lab

    View Slide

  12. gCloud
    Tool to run tests on Firebase
    Integrates with CI

    View Slide

  13. ~ gCloud firebase test android models list

    View Slide

  14. MODEL_ID MAKE MODEL_NAME FORM RESOLUTION OS
    Nexus4 LG Nexus 4 VIRTUAL 2560 x 1600 19,21,22
    sailfish Google Pixel PHYSICAL 1080 x 1920 25, 26
    ~ gCloud firebase test android models list

    View Slide

  15. ~ gCloud firebase test android run
    —type
    robo tests
    instrumentation

    View Slide

  16. ~ gCloud firebase test android run
    —type instrumentation
    —app app-debug-unaligned.apk

    View Slide

  17. ~ gCloud firebase test android run
    —type instrumentation
    —app app-debug-unaligned.apk
    —device model=Nexus6, version=21 local=en, orientation=portrait

    View Slide

  18. Upload
    Google Cloud Storage

    View Slide

  19. Google Cloud Storage
    Run Tests
    Report

    View Slide

  20. View Slide

  21. ~ gCloud firebase test android run
    —type robo

    View Slide

  22. View Slide

  23. Get Report
    Test Lab
    gCloud
    Build APK
    CI Pipeline

    View Slide

  24. UI Tests
    Time

    View Slide

  25. Flank
    Run Android and iOS tests in parallel
    Uses Kotlin Coroutines

    View Slide

  26. Flank
    flank:

    app: ./app-debug.apk

    test: ./app-debug-androidTest.apk

    View Slide

  27. Flank
    flank:

    app: ./app-debug.apk

    test: ./app-debug-androidTest.apk

    device:

    - model: NexusLowRes

    version: 28

    View Slide

  28. Test Case
    Test Case
    Test Case
    Test Case
    Parallel

    View Slide

  29. Flank
    flank:


    ##
    test shards - the amount of groups to split the test suite into

    max-test-shards: 2

    View Slide

  30. Flank
    flank:


    ##
    test shards - the amount of groups to split the test suite into

    max-test-shards: 2

    ##
    shard time - the amount of time tests within a shard should take

    shard-time: 2

    View Slide

  31. ~ flank android test run

    View Slide

  32. Test Case
    Test Case
    Test Case
    Test Case
    Parallel

    View Slide

  33. Merged Results
    Shared 2
    Parallel
    Shard 1

    View Slide

  34. Test Case
    Test Case
    Test Case
    Test Case
    Problems
    Flaky tests

    View Slide

  35. Flaky Test Causes
    ● Concurrency problems

    ● Flaky third party code

    ● Resource idling.

    View Slide

  36. Flank
    flank:


    ##
    Number of Flaky test attempts

    num-flaky-test-attempts: 2

    View Slide

  37. CI Setup
    Get Report
    Test Lab
    Flank
    Build APK

    View Slide

  38. CI Setup
    Get Report
    Test Lab
    Flank
    Build APK
    Docker

    View Slide

  39. Fladle
    Gradle plugin for using Flank
    Multi Module Testing support

    View Slide

  40. Recipes
    Performance
    Regression

    View Slide

  41. Recipes
    perfTests {

    devices.set([

    ["model" : "Nexus5", "version" : "28"],

    ])

    testTargets.set([

    "class com.test.PerformanceTest"

    ])

    }

    View Slide

  42. Recipes
    regressionTests {

    devices.set([

    [ "model" : "Nexus4", "version" : "28"]

    ])

    testTargets.set([

    "class com.sample.MyRegressionTest"

    ])

    }

    View Slide

  43. Problems
    ● Tests take too long to run

    ● Flaky tests

    ● When do you run UI tests?

    View Slide

  44. Nightly CI Job
    Running UI Tests
    Get Report
    Regression

    Tests
    Fladle
    Build APK

    View Slide

  45. Smoke Tests
    ● Test basic functionality

    ● Run on PRs

    View Slide

  46. Smoke Tests
    @Retention(AnnotationRetention.RUNTIME)

    annotation class SmokeTest

    View Slide

  47. Example - Firefox App
    ● Open source

    ● Nightly & Smoke tests

    ● Shards with Flank

    View Slide

  48. https:
    /
    /
    github.com/mozilla-mobile/fenix

    View Slide

  49. Firefox App
    Test Rails
    Smoke

    Tests
    Fladle
    Build APK

    View Slide

  50. Test Rails
    ● Documentation for your test suite

    ● View results over time

    ● Collaborate with QA

    View Slide

  51. View Slide

  52. View Slide

  53. UI Tests Infra
    ● Run tests in parallel

    ● Use Flank with Firebase test lab

    ● Flaky tests

    View Slide

  54. Unit Testing Infrastructure

    View Slide

  55. Unit Testing
    Integration
    E2E
    Slower,

    more expensive
    Faster,

    cheaper

    View Slide

  56. How do we structure unit tests?

    View Slide

  57. Problem
    class Presenter(

    val repo: Repo,

    ...


    )

    View Slide

  58. Problem
    interface Repo {


    fun getData(): Data

    
 ...


    }

    View Slide

  59. Structure
    Feature (folder)
    Public (Module)
    Impl (Module)
    Fakes (Module)

    View Slide

  60. Structure
    class PresenterTest {


    val fakeRepo = FakeRepository()


    val presenter = Presenter(fakeRepo)

    }

    View Slide

  61. Structure
    Feature (folder)
    Public (Module)
    Impl (Module)
    Fakes (Module)

    View Slide

  62. Dev Tools
    ● Auto Generate module scaffolding

    ● Enforce rules on how modules depends on each other

    View Slide

  63. Unit Testing Lint Rules
    ● Enforce best practices

    View Slide

  64. Unit Testing Lint Rules
    ● Enforce best practices

    ● Coroutine lint test rules

    View Slide

  65. Unit Testing Lint Rules
    ● Enforce best practices

    ● Coroutine lint test rules

    ● Do not mock data classes

    View Slide

  66. Unit Tests
    Time

    View Slide

  67. Problem
    bdae142
    ..
    main
    50157a
    ..
    d89f145
    ..
    f0ddfb
    ..

    View Slide

  68. View Slide

  69. Run only affected unit tests

    View Slide

  70. Affected Module Dectector
    Gradle plugin for determine which files changed
    Run only affected tests

    View Slide

  71. Affected Module Detector
    App
    Module A Module B
    Module C

    View Slide

  72. Affected Module Detector
    affectedModuleDetector {

    baseDir = "${project.rootDir}"

    compareFrom = "PreviousCommit"

    }

    View Slide

  73. Affected Module Detector
    affectedModuleDetector {

    baseDir = "${project.rootDir}"

    compareFrom = "PreviousCommit"

    }

    Fork Commit

    SpecifiedBranchCommit

    View Slide

  74. Affected Module Detector
    App
    Module A Module B
    Module C
    assembleAndroidDebugTest

    connectedAndroidDebugTest

    testDebug

    View Slide

  75. Affected Module Detector
    ~ ./gradlew runAffectedUnitTests
    //
    Runs jvm tests

    View Slide

  76. Affected Module Detector
    ~ ./gradlew runAffectedAndroidTests
    //
    Runs UI tests

    View Slide

  77. Run only affected unit tests

    View Slide

  78. Unit Testing
    Integration
    E2E
    Slower,

    more expensive
    Faster,

    cheaper

    View Slide

  79. Problem
    ● Run only affected unit tests

    ● Snapshot testing with unit tests

    View Slide

  80. Problem
    ● UI Regressions

    ● Example - Constraint layout changes

    View Slide

  81. Paparazzi
    Gradle plugin to generate screenshot with unit tests
    Supports Compose UI

    View Slide

  82. Paparazzi
    @get:Rule

    val paparazzi = Paparazzi(

    deviceConfig = PIXEL_5,

    theme = “android.Theme.Material.Light.NoActionBar”

    )

    View Slide

  83. Paparazzi
    @get:Rule

    val paparazzi = Paparazzi(
    ...
    )

    @Test

    fun testView() {

    paparazzi.snapshot {

    UiView(uiState)

    }

    }

    View Slide

  84. Paparazzi
    Git (LFS)
    Snapshot

    View Slide

  85. Run Paparazzi tests

    View Slide

  86. Paparazzi
    ~ ./gradlew app:recordPaparazziDebug
    //
    Generate Report

    View Slide

  87. Paparazzi
    ~ ./gradlew app:verifyPaparazziDebug
    //
    Run again previously recorded

    View Slide

  88. Problem
    ● Run only affected unit tests

    ● Creating snapshot with unit testing

    ● Unit testing database migrations

    View Slide

  89. Database Migrations

    View Slide

  90. Database Migrations
    id user_name user_email
    1 User 1 [email protected]
    2 User 2 [email protected]
    User Table

    View Slide

  91. Database Migrations
    id user_name user_email
    1 User 1 [email protected]
    2 User 2 [email protected]
    User Table
    Rename to “email”

    View Slide

  92. Database Migrations
    @RenameColumn(

    tableName = "users",

    fromColumnName = "user_email",

    toColumnName = "email"

    )

    class RenameFromUserAddressToAddress : AutoMigrationSpec

    View Slide

  93. Database Migrations
    @Database(

    version = 2,

    autoMigrations = [

    AutoMigration(

    from = 1, to = 2,

    spec = UserDatabase.RenameFromUserAddressToAddress
    ::
    class

    ),

    ],

    )

    View Slide

  94. How do we test migrations?

    View Slide

  95. Test Migrations
    @get:Rule

    val helper: MigrationTestHelper = MigrationTestHelper(

    UserDatabase
    ::
    class.java,

    listOf(

    UserDatabase.RenameFromUserAddressToAddress()

    ),

    )

    View Slide

  96. Test Migrations
    @Test

    fun migrate1To2() {

    db = helper.createDatabase(TEST_DB, 1).apply {

    execSQL(

    """

    INSERT INTO users VALUES (1, ‘User 1', ‘[email protected]')
    """.trimIndent()

    )

    close()

    }

    }

    View Slide

  97. Test Migrations
    @Test

    fun migrate1To2() {


    db = helper.runMigrationsAndValidate(TEST_DB, 2, true)

    }

    View Slide

  98. Test Migrations
    @Test

    fun migrate1To2() {


    val resultCursor = db.query("SELECT * FROM users”)

    //
    Perform assertions

    }

    View Slide

  99. Problem
    ● Test migrations with unit tests

    View Slide

  100. Database Migrations
    JDBC SQLite Driver

    View Slide

  101. DB Tools Room for Android
    Test Room with databases
    Migration testing

    View Slide

  102. https:
    //
    github.com/jeffdcamp/dbtools-room

    View Slide

  103. Test Migrations
    class MigrationTest: BaseMigrationTest()

    View Slide

  104. Test Migrations
    val db = Room.databaseBuilder(
    ...
    )

    .openHelperFactory(JdbcSQLiteOpenHelperFactory(…))

    .build()

    View Slide

  105. Test Migrations
    fun testMigration(fromVersion: Int, toVersion: Int) {

    migrationTestExtension.createDatabase(name, fromVersion)

    migrationTestExtension.runMigrationsAndValidate(

    name,

    toVersion,

    *migrations

    )

    }

    View Slide

  106. Problem
    ● Run only affected unit tests

    ● Creating snapshot with unit testing

    ● Unit testing database migrations

    View Slide

  107. Test Coverage
    Gradle Builds
    Database
    UI
    API

    View Slide

  108. Gradle Testing

    View Slide

  109. Problem
    ● Detect build regressions

    View Slide

  110. Gradle Profiler
    Change

    View Slide

  111. Gradle Profiler
    1. Write a performance scenario

    2. Specify number of iterations

    View Slide

  112. Gradle Profiler
    Iteration 1
    Iteration 2
    Iteration 3

    View Slide

  113. Gradle Profiler
    Iteration 1
    Iteration 2
    Iteration 3
    Abi Change

    View Slide

  114. Gradle Profiler
    Iteration 1
    Iteration 2
    Iteration 3
    Result

    View Slide

  115. Performance Scenario
    incremental_build {

    apply-abi-change-to = “src/main/java/StringUtils.kt”

    }

    View Slide

  116. Performance Scenario
    incremental_build {

    apply-abi-change-to = “src/main/java/StringUtils.kt”

    apply-android-resource-change-to = “strings.xml”

    tasks = ["assemble"]

    }

    View Slide

  117. Gradle Profiler
    gradle-profiler —benchmark —iterations=10 —warmups=6

    View Slide

  118. Gradle Profiler
    Mean: 348 ms

    Min: 319 ms

    P25: 330 ms

    Median: 341 ms

    P75: 368 ms

    Std dev: 22.71 ms

    View Slide

  119. Gradle Profiler

    View Slide

  120. How do we automate profiling on CI?

    View Slide

  121. CI Pipeline
    Docker image with

    Gradle Profiler

    View Slide

  122. CI Pipeline
    Docker image with

    Gradle Profiler
    Performance

    Scenarios

    View Slide

  123. CI Pipeline
    Docker image with

    Gradle Profiler
    Performance

    Scenarios
    Run

    nightly

    View Slide

  124. Use Cases
    • Introducing Anvil

    View Slide

  125. Benchmarking Build
    • Benchmark with Anvil change

    • Benchmark without Anvil change (Baseline)

    • Compare results

    View Slide

  126. Results
    Module A Module B Module C
    Baseline Anvil

    View Slide

  127. Building Android Testing Infrastructure
    ● Automation Tools

    ● Building Infra pipelines

    ● Snapshot Testing

    ● Gradle Regressions

    View Slide

  128. Thank You!
    www.codingwithmohit.com
    @heyitsmohit

    View Slide