$30 off During Our Annual Pro Sale. View Details »

An introduction Effective Snapshot Testing on Android

An introduction Effective Snapshot Testing on Android

Talk on Snapshot testing at Droidcon Berlin 2021 & Droidcon London 2021
-------------------------------------------------------------------------------
UI tests are a powerful tool to detect regression bugs. However, they are tedious to write and run very slow. But they are not the only tool we got to test the UI:
snapshot tests are some specific type of UI tests, that are not only easier to write but also run much faster than standard UI tests. They even help detect visual regression bugs that Espresso/UiAutomator tests cannot. Indeed, they are more widely used than UI tests by big companies such as Airbnb, with ≈ 30.000 snapshot tests, and Uber, with thousand of snapshot tests vs. a handful of UI tests, among others.

While there are plenty of resources about writing UI tests on Android, the same doesn't exist for snapshot tests. There is no guidance.

So, what is actually snapshot testing? how do you write a valuable snapshot test? what are the best practices? what are the pitfalls you have to take into account?

If you want to know how to get the most out of snapshot testing, come and join this tech talk. You will learn:
- what is snapshot testing and how your app can benefit from it
- how to decide what to snapshot test
- how to write your first meaningful snapshot test
- tips and tricks for better snapshot testing and what pitfalls to avoid
-------------------------------------------------------------------------------
Repo with code examples:
https://github.com/sergio-sastre/Road-To-Effective-Snapshot-Testing

Sergio Sastre Flórez

October 30, 2021
Tweet

More Decks by Sergio Sastre Flórez

Other Decks in Programming

Transcript

  1. EFFECTIVE SNAPSHOT TESTING
    ON ANDROID
    BY SERGIO SASTRE
    AN INTRODUCTION TO

    View Slide

  2. WHAT
    Snapshot testing
    is

    View Slide

  3. Special type of UI tests
    take a screenshot of it,
    compare it to its reference
    we inflate a view,
    where
    and

    View Slide

  4. Snapshot tests Ui tests
    thousand handful
    Source: "Building Mobile Apps at scale - 39 Engineering Challenges"

    View Slide

  5. Snapshot tests Ui tests
    thousand handful
    ≈ 1 600 ≈ 500
    Source: "Building Mobile Apps at scale - 39 Engineering Challenges"

    View Slide

  6. Snapshot tests Ui tests
    thousand handful
    ≈ 1 600 ≈ 500
    ≈ 2 300 ≈ 20
    Source: "Building Mobile Apps at scale - 39 Engineering Challenges"

    View Slide

  7. Snapshot tests Ui tests
    thousand handful
    ≈ 1 600 ≈ 500
    ≈ 2 300 ≈ 20
    ≈ 30 000 ?
    Source: "Building Mobile Apps at scale - 39 Engineering Challenges"

    View Slide

  8. Ui tests
    Snapshot tests
    Icons made by “Freepik" from "Flaticon"
    writing speed

    View Slide

  9. Ui tests
    Snapshot tests
    Icons made by “Freepik" from "Flaticon"
    writing speed

    View Slide

  10. Ui tests
    Snapshot tests
    5-
    10
    sec
    Icons made by “Freepik" from "Flaticon"
    writing speed

    View Slide

  11. Ui tests
    Snapshot tests
    5-
    10
    sec
    Icons made by “Freepik" from "Flaticon"
    writing speed

    View Slide

  12. Ui tests
    Snapshot tests
    5-
    10
    sec
    1
    sec
    <
    Icons made by “Freepik" from "Flaticon"
    writing speed

    View Slide

  13. VS
    Snapshot

    tests
    User interface


    tests

    View Slide

  14. Snapshot

    tests
    User interface


    tests VS

    View Slide

  15. Snapshot

    tests
    User interface


    tests
    +

    View Slide

  16. Snapshot

    tests
    User interface


    tests
    +
    (interaction)
    WHAT

    View Slide

  17. Snapshot

    tests
    User interface


    tests
    +
    (interaction)
    WHAT HOW
    (visuals)

    View Slide

  18. MOTIVATION
    Snapshot test
    to

    View Slide

  19. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  20. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  21. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  22. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  23. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  24. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs
    which will expand till the end of the line and …

    View Slide

  25. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  26. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  27. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  28. Unexpected changes from library updates
    Spacing, styling, themes…
    Layout correctness
    Long text
    View overlapping
    Languages (RTL)
    System font size (HUGE)
    1
    2
    3
    visual bugs

    View Slide

  29. Example

    View Slide

  30. Layout validation

    View Slide

  31. Layout validation

    View Slide

  32. Settings Display Font size
    System font size
    >
    >

    View Slide

  33. Example

    View Slide

  34. Example

    View Slide

  35. Font Size
    Largest


    6,16%
    Small


    8,06%
    Large


    14,22%
    Default


    71,56%
    TT
    Source: Android app with 80 000 monthly active users

    View Slide

  36. Font Size
    Small


    8,06%
    Default


    71,56%
    TT
    20, 38 %
    Large + Largest
    Source: Android app with 80 000 monthly active users

    View Slide

  37. Do not trust Layout validation
    BUT
    Manual validation is hard

    View Slide

  38. 1
    Snapshot tests
    Use
    2
    Automate hard configs
    Catch regression bugs

    View Slide

  39. WORKS
    HOW
    Snapshot testing

    View Slide

  40. record
    AND
    verify

    View Slide

  41. write
    test
    CI

    View Slide

  42. record
    CI

    View Slide

  43. =
    CI

    View Slide

  44. CI
    new

    PR
    +

    View Slide

  45. approve
    CI

    View Slide

  46. merge
    CI

    View Slide

  47. CI
    new
    PR
    verify

    View Slide

  48. CI
    new
    PR

    View Slide

  49. CI
    new
    PR

    View Slide

  50. CI
    new
    PR

    View Slide

  51. CI
    new
    PR
    verify

    View Slide

  52. CI
    new
    PR

    View Slide

  53. CI
    new
    PR

    View Slide

  54. CI
    new
    PR

    View Slide

  55. CI
    Intentional change
    forgot to


    record
    regression


    bug
    YES
    NO

    View Slide

  56. CI
    Emulator config matters
    D1
    DN
    .
    .
    .

    View Slide

  57. CI
    Emulator config matters
    =
    D1
    DN
    =
    .
    .
    .
    =
    OR

    View Slide

  58. CI
    Emulator config matters
    D1
    =
    .
    .
    .
    DN
    =
    =
    OR

    View Slide

  59. CI
    Emulator config matters
    D1
    DN
    .
    .
    .
    =
    =

    View Slide

  60. CI
    Emulator config matters
    =
    D1
    =
    =
    .
    .
    .
    DN
    OR

    View Slide

  61. CI
    Emulator config matters
    =
    D1
    =
    =
    .
    .
    .
    DN
    OR

    View Slide

  62. 1
    2
    More complex than


    standard testing
    Same emulator config


    everywhere
    process
    Snapshot testing

    View Slide

  63. How to
    Snapshot test
    EFFECTIVELY

    View Slide

  64. Example

    View Slide

  65. Example

    View Slide

  66. Dealing with asynchronicity
    Choosing test-worthy screen states
    More views, more prone to flakiness
    1
    2
    3
    snapshot testing
    full screen

    View Slide

  67. Dealing with asynchronicity
    Choosing test-worthy screen states
    More views, more prone to flakiness
    1
    2
    3
    snapshot testing
    full screen

    View Slide

  68. Dealing with asynchronicity
    Choosing test-worthy screen states
    More views, more prone to flakiness
    1
    2
    3
    snapshot testing
    full screen

    View Slide

  69. Dealing with asynchronicity
    Choosing test-worthy screen states
    More views, more prone to flakiness
    1
    2
    3
    snapshot testing
    full screen

    View Slide

  70. Dealing with asynchronicity
    Choosing test-worthy screen states
    More views, more prone to flakiness
    1
    2
    3
    snapshot testing
    full screen

    View Slide

  71. start small & simple
    Header
    Training View
    Text
    view
    to memorise
    1
    split into smaller testable views

    View Slide

  72. Header
    Training View
    Text
    view
    to memorise
    start small & simple
    1
    what to test

    View Slide

  73. Header
    Training View
    Text
    view
    to memorise
    start small & simple
    1
    what to test

    View Slide

  74. A
    B
    start small & simple
    1
    Identifying view states

    View Slide

  75. A
    B
    start small & simple
    1
    Identifying view states

    View Slide

  76. aim to break the layout
    2
    Identifying unhappy paths

    View Slide

  77. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train

    View Slide

  78. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train
    words to train for all 7 languages

    View Slide

  79. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train
    words to train for all 7 languages
    huge system font size

    View Slide

  80. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train
    words to train for all 7 languages
    huge system font size
    narrow screen

    View Slide

  81. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train
    words to train for all 7 languages
    huge system font size
    narrow screen

    View Slide

  82. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train
    words to train for all 7 languages
    huge system font size
    narrow screen
    trainingInfo =

    TrainingItem
    (

    trainingByLang = wordsToTrainPerLang(999_999)
    ,

    activeLangs = Language.values().toSet(
    )

    ),
    TrainingTestItem(
    fontSize = FontScale.HUGE,
    viewWidth = ViewWidth.NARROW
    fun withWordsToTrainUnhappyPath() =
    )

    )
    Mock Data

    View Slide

  83. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train
    words to train for all 7 languages
    huge system font size
    narrow screen
    trainingInfo =

    TrainingItem
    (

    trainingByLang = wordsToTrainPerLang(999_999)
    ,

    activeLangs = Language.values().toSet(
    )

    ),
    TrainingTestItem(
    fontSize = FontScale.HUGE,
    viewWidth = ViewWidth.NARROW
    fun withWordsToTrainUnhappyPath() =
    )

    )

    View Slide

  84. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train
    words to train for all 7 languages
    huge system font size
    training

    item
    narrow screen
    trainingInfo =

    TrainingItem
    (

    trainingByLang = wordsToTrainPerLang(999_999)
    ,

    activeLangs = Language.values().toSet(
    )

    ),
    TrainingTestItem(
    fontSize = FontScale.HUGE,
    viewWidth = ViewWidth.NARROW
    )
    fun withWordsToTrainUnhappyPath() =
    )

    )

    View Slide

  85. aim to break the layout
    2
    Identifying unhappy paths
    thousand of words to train
    words to train for all 7 languages
    huge system font size
    narrow screen
    trainingInfo =

    TrainingItem
    (

    trainingByLang = wordsToTrainPerLang(999_999)
    ,

    activeLangs = Language.values().toSet(
    )

    ),
    TrainingTestItem(
    fontSize = FontScale.HUGE,
    viewWidth = ViewWidth.NARROW
    )

    )
    external
    fun withWordsToTrainUnhappyPath() =

    View Slide

  86. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    private fun ScreenshotTest.snapViewHolder(testItem: TrainingTestItem, snapshotName: String){
    val activity = FontsizeActivityScenario.launchWith(testItem.fontScale
    )

    .waitForActivity(),
    val view = waitForView {

    val layout = activity.inflate(R.layout.training_row)

    TrainingViewHolder(layout).apply
    {

    bind
    (

    trainingItem = testItem.trainingItem,

    languageClickedListener = null

    )

    }

    }
    compareScreenshot {

    holder = view,

    widthInPx = testItem.viewWidth.widthInPx
    ,

    name = snapshotName

    }
    }
    Test itself

    View Slide

  87. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    private fun ScreenshotTest.snapViewHolder(testItem: TrainingTestItem, snapshotName: String){
    val activity = FontsizeActivityScenario.launchWith(testItem.fontScale
    )

    .waitForActivity(),
    val view = waitForView {

    val layout = activity.inflate(R.layout.training_row)

    TrainingViewHolder(layout).apply
    {

    bind
    (

    trainingItem = testItem.trainingItem,

    languageClickedListener = null

    )

    }

    }
    compareScreenshot {

    holder = view,

    widthInPx = testItem.viewWidth.widthInPx
    ,

    name = snapshotName

    }
    }

    View Slide

  88. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    private fun ScreenshotTest.snapViewHolder(testItem: TrainingTestItem, snapshotName: String){
    val activity = FontsizeActivityScenario.launchWith(testItem.fontScale
    )

    .waitForActivity(),
    val view = waitForView {

    val layout = activity.inflate(R.layout.training_row)

    TrainingViewHolder(layout).apply
    {

    bind
    (

    trainingItem = testItem.trainingItem,

    languageClickedListener = null

    )

    }

    }
    compareScreenshot {

    holder = view,

    widthInPx = testItem.viewWidth.widthInPx
    ,

    name = snapshotName

    }
    }
    Wait for

    Ui thread

    idle
    +
    launch

    ActivityScenario

    View Slide

  89. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    private fun ScreenshotTest.snapViewHolder(testItem: TrainingTestItem, snapshotName: String){
    val activity = FontsizeActivityScenario.launchWith(testItem.fontScale
    )

    .waitForActivity(),
    val view = waitForView {

    val layout = activity.inflate(R.layout.training_row)

    TrainingViewHolder(layout).apply
    {

    bind
    (

    trainingItem = testItem.trainingItem,

    languageClickedListener = null

    )

    }

    }
    compareScreenshot {

    holder = view,

    widthInPx = testItem.viewWidth.widthInPx
    ,

    name = snapshotName

    }
    }
    Inflate view

    &


    bind data
    Wait for

    Ui thread

    idle
    +

    View Slide

  90. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    private fun ScreenshotTest.snapViewHolder(testItem: TrainingTestItem, snapshotName: String){
    val activity = FontsizeActivityScenario.launchWith(testItem.fontScale
    )

    .waitForActivity(),
    val view = waitForView {

    val layout = activity.inflate(R.layout.training_row)

    TrainingViewHolder(layout).apply
    {

    bind
    (

    trainingItem = testItem.trainingItem,

    languageClickedListener = null

    )

    }

    }
    compareScreenshot {

    holder = view,

    widthInPx = testItem.viewWidth.widthInPx
    ,

    name = snapshotName

    }
    }
    record


    screenshot

    View Slide

  91. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    private fun ScreenshotTest.snapViewHolder(testItem: TrainingTestItem, snapshotName: String){
    val activity = FontsizeActivityScenario.launchWith(testItem.fontScale
    )

    .waitForActivity(),
    val view = waitForView {

    val layout = activity.inflate(R.layout.training_row)

    TrainingViewHolder(layout).apply
    {

    bind
    (

    trainingItem = testItem.trainingItem,

    languageClickedListener = null

    )

    }

    }
    compareScreenshot {

    holder = view,

    widthInPx = testItem.viewWidth.widthInPx
    ,

    name = snapshotName

    }
    }

    View Slide

  92. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(private val testItem: TrainingTestItem): ScreenshotTest{
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath()

    ..
    .

    )

    }
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    Parameterized

    tests

    View Slide

  93. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(private val testItem: TrainingTestItem): ScreenshotTest{
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath()

    ..
    .

    )

    }
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }

    View Slide

  94. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(private val testItem: TrainingTestItem): ScreenshotTest{
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath()

    ..
    .

    )

    }
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    set

    Runner

    View Slide

  95. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(private val testItem: TrainingTestItem): ScreenshotTest{
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath()

    ..
    .

    )

    }
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    generate

    test data

    View Slide

  96. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(private val testItem: TrainingTestItem): ScreenshotTest{
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath()

    ..
    .

    )

    }
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    test with

    parameter

    View Slide

  97. aim to break the layout
    2
    writing the snapshot test with pedrovgs/Shot
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(private val testItem: TrainingTestItem): ScreenshotTest{
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath()

    ..
    .

    )

    }
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }

    View Slide

  98. aim to break the layout
    2
    thousand of words to train
    words to train for all 7 languages
    huge system font size
    narrow screen
    recording unhappy path

    View Slide

  99. aim to break the layout
    2
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(private val testItem: TrainingTestItem): ScreenshotTest{
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath()

    ..
    .

    )

    }
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    recording happy and unhappy paths

    View Slide

  100. aim to break the layout
    2
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(private val testItem: TrainingTestItem): ScreenshotTest{
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(), withWordsToTrainHappyPath()

    withoutWordsToTrainUnhappyPath(), withoutWordsToTrainHappyPath(
    )

    )

    }
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    recording happy and unhappy paths

    View Slide

  101. aim to break the layout
    2
    recording happy and unhappy paths
    without WordsToTrain HappyPath()
    with WordsToTrain HappyPath() with WordsToTrain UnhappyPath()
    without WordsToTrain UnhappyPath()

    View Slide

  102. group tests by relevance
    3

    View Slide

  103. 2’ 100 happy + unhappy
    group tests by relevance
    3
    100%

    View Slide

  104. 2’
    4’ 200
    100 happy + unhappy
    group tests by relevance
    3
    100%

    View Slide

  105. 2’
    4’
    8’ 400
    200
    100 happy + unhappy
    group tests by relevance
    3
    100%

    View Slide

  106. 2’
    4’
    8’
    16’ 800
    400
    200
    100 happy + unhappy
    group tests by relevance
    3
    100%

    View Slide

  107. 2’
    4’
    8’
    16’

    800
    400
    200
    100 happy + unhappy
    100%
    group tests by relevance
    3

    View Slide

  108. 2’
    4’
    8’
    16’ 800
    400
    200
    100
    group tests by relevance
    3
    unhappy
    happy 90 - 100%
    5 - 20%

    View Slide

  109. 2’

    1’
    3’
    40
    80
    160
    320
    6’
    unhappy
    happy 90 - 100%
    5 - 20%
    group tests by relevance
    3

    View Slide

  110. daily builds night build

    View Slide

  111. night build
    run happy path tests ONLY
    catch most important bugs
    run on every PR
    daily builds

    View Slide

  112. night build
    run happy path tests ONLY
    catch most important bugs
    run on every PR
    run unhappy path tests ONLY
    catch less important bugs
    run once a day
    daily builds

    View Slide

  113. night build
    run happy path tests ONLY
    catch most important bugs
    run on every PR
    run unhappy path tests ONLY
    catch less important bugs
    run once a day
    daily builds
    fast development cycles

    View Slide

  114. 3
    unhappy vs. happy path
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    group tests by relevance
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(), withWordsToTrainHappyPath()

    withoutWordsToTrainUnhappyPath(), withoutWordsToTrainHappyPath(
    )

    )

    }
    Filtered


    parameterized

    tests

    View Slide

  115. 3
    unhappy vs. happy path
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    group tests by relevance
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(), withWordsToTrainHappyPath()

    withoutWordsToTrainUnhappyPath(), withoutWordsToTrainHappyPath(
    )

    )

    }

    View Slide

  116. 3
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    group tests by relevance
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(), withWordsToTrainHappyPath()

    withoutWordsToTrainUnhappyPath(), withoutWordsToTrainHappyPath(
    )

    )

    }
    unhappy vs. happy path

    View Slide

  117. 3
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    group tests by relevance
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(),

    withoutWordsToTrainUnhappyPath(
    )

    )

    }
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainHappyPath()

    withoutWordsToTrainHappyPath(
    )

    )

    }
    @RunWith(Parameterized::class)

    class TrainingItemHappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    unhappy vs. happy path

    View Slide

  118. 3
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    group tests by relevance
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(),

    withoutWordsToTrainUnhappyPath(
    )

    )

    }
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainHappyPath()

    withoutWordsToTrainHappyPath(
    )

    )

    }
    @RunWith(Parameterized::class)

    class TrainingItemHappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    unhappy vs. happy path

    View Slide

  119. 3
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @UnhappyPat
    h

    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    group tests by relevance
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(),

    withoutWordsToTrainUnhappyPath(
    )

    )

    }
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainHappyPath()

    withoutWordsToTrainHappyPath(
    )

    )

    }
    @RunWith(Parameterized::class)

    class TrainingItemHappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @HappyPat
    h

    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    unhappy vs. happy path

    View Slide

  120. 3
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @UnhappyPat
    h

    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    group tests by relevance
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(),

    withoutWordsToTrainUnhappyPath(
    )

    )

    }
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainHappyPath()

    withoutWordsToTrainHappyPath(
    )

    )

    }
    @RunWith(Parameterized::class)

    class TrainingItemHappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @HappyPat
    h

    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    = path.to.annotation.UnhappyPath
    -Pandroid.testIntrumentationRunnerArguments.annotation
    ./gradlew -Precord
    unhappy vs. happy path

    View Slide

  121. 3
    @RunWith(Parameterized::class)

    class TrainingItemUnhappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @UnhappyPat
    h

    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    group tests by relevance
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainUnhappyPath(),

    withoutWordsToTrainUnhappyPath(
    )

    )

    }
    companion object {

    @JvmStatic

    @Parameterized.Parameter
    s

    fun testData(): Array =
    arrayOf
    (

    withWordsToTrainHappyPath()

    withoutWordsToTrainHappyPath(
    )

    )

    }
    @RunWith(Parameterized::class)

    class TrainingItemHappyPath(

    private val testItem: TrainingTestItem

    ): ScreenshotTest{
    @HappyPat
    h

    @Test

    fun snapTrainingItem(){


    snapViewHolder(testItem)

    )
    }
    = path.to.annotation.HappyPath
    -Pandroid.testIntrumentationRunnerArguments.annotation
    ./gradlew -Precord
    unhappy vs. happy path

    View Slide

  122. 1
    Snapshot testing
    effectively
    2
    Start small & simple
    Aim to break the layout
    3 Group tests by relevance

    View Slide

  123. attention
    for your
    THANKS

    View Slide

  124. code
    blogs
    @SergioSastre
    @sergio-sastre
    @sergio-sastre
    @Gio_Sastre
    Sergio Sastre Flórez
    Lead & Senior
    Android developer appdev.de

    View Slide