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

Pragmatic Testing

Pragmatic Testing

(given at Droidcon Italy 2018 and Android Makers 2018)

You open a pull request only to find that some tests are now failing. What goes through your mind?

Did I break something? Do I need to update the tests? Just what is that test testing anyway?

Ideally, #2 would almost never be the case, as rewriting a test usually means it's lost the regression testing value of its original form. How can we get there though? There’s no one right way to write tests, and it’s surprisingly easy to fall into the trap of writing change tests. There are good patterns you can leverage to scale them and maximize their long term value. This talk will detail simple patterns you can follow for healthy, pragmatic testing with a focus on test design, correctness, and how these values can translate into better overall API design of your code.

Zac Sweers

April 24, 2018
Tweet

More Decks by Zac Sweers

Other Decks in Programming

Transcript

  1. Pragmatic
    Testing
    Zac Sweers - Uber
    @pandanomic

    View Slide

  2. Testing?

    View Slide

  3. Testing!

    View Slide

  4. @Test

    View Slide

  5. @Test

    View Slide

  6. Pragmatic
    Testing

    View Slide

  7. Pragmatic?
    Testing

    View Slide

  8. Goals ⚽

    View Slide

  9. Non-Goals

    View Slide

  10. Non-Goals
    • Convince you testing is worthwhile

    View Slide

  11. Non-Goals
    • Convince you testing is worthwhile
    • UI Testing (Espresso, layouts, etc)

    View Slide

  12. Non-Goals
    • Convince you testing is worthwhile
    • UI Testing (Espresso, layouts, etc)
    • Knock specific libraries

    View Slide

  13. View Slide

  14. Boundaries

    View Slide

  15. Calculator

    View Slide

  16. Calculator
    add()
    subtract()
    multiply()
    divide()
    equal()
    clear()

    View Slide

  17. Calculator
    InputManager
    DisplayManager
    CPU
    RAM
    add()
    subtract()
    multiply()
    divide()
    equal()
    clear()

    View Slide

  18. InputManager
    DisplayManager
    CPU
    RAM
    add()
    subtract()
    multiply()
    divide()
    equal()
    clear()

    View Slide

  19. InputManager
    DisplayManager
    CPU
    RAM
    add()
    subtract()
    multiply()
    divide()
    equal()
    clear()

    View Slide

  20. InputManager
    DisplayManager
    CPU
    RAM
    add()
    subtract()
    multiply()
    divide()
    equal()
    clear()

    View Slide

  21. InputManager
    DisplayManager
    CPU
    RAM
    add()
    subtract()
    multiply()
    divide()
    equal()
    clear()

    View Slide

  22. InputManager DisplayManager
    CPU RAM

    View Slide

  23. InputManager DisplayManager
    CPU RAM

    View Slide

  24. @Test
    fun testAdd() {
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  25. @Test
    fun testAdd() {
    val calculator = Calculator()
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  26. @Test
    fun testAdd() {
    val cpu = mock(CPU::class.java)
    val calculator = Calculator()
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  27. @Test
    fun testAdd() {
    val cpu = mock(CPU::class.java)
    val ram = mock(RAM::class.java)
    val calculator = Calculator()
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  28. @Test
    fun testAdd() {
    val cpu = mock(CPU::class.java)
    val ram = mock(RAM::class.java)
    val display = mock(DisplayManager::class.java)
    val calculator = Calculator()
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  29. @Test
    fun testAdd() {
    val cpu = mock(CPU::class.java)
    val ram = mock(RAM::class.java)
    val display = mock(DisplayManager::class.java)
    val input = mock(InputManager::class.java)
    val calculator = Calculator()
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  30. @Test
    fun testAdd() {
    val cpu = mock(CPU::class.java)
    val ram = mock(RAM::class.java)
    val display = mock(DisplayManager::class.java)
    val input = mock(InputManager::class.java)
    val calculator = Calculator(cpu, ram, display, input)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  31. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  32. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  33. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  34. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  35. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  36. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  37. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  38. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  39. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    verify(input).onInputReceived(2)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  40. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    }a

    View Slide

  41. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    }a

    View Slide

  42. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    verify(cpu).calculateSum(2, 2)
    }a

    View Slide

  43. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a

    View Slide

  44. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    input.add(2, 2)
    verify(cpu).setRam(ram)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a

    View Slide

  45. @Test
    fun testAdd() {
    input.add(2, 2)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  46. @Test
    fun testAdd() {
    input.add(2, 2)
    }a
    registerInputListener(listener: Listener)
    InputManager DisplayManager
    CPU RAM

    View Slide

  47. val captor = ArgumentCaptor.forClass(Listener::class.java)
    InputManager DisplayManager
    CPU RAM

    View Slide

  48. val captor = ArgumentCaptor.forClass(Listener::class.java)
    verify(input).registerInputListener(captor.capture())
    InputManager DisplayManager
    CPU RAM

    View Slide

  49. val captor = ArgumentCaptor.forClass(Listener::class.java)
    verify(input).registerInputListener(captor.capture())
    val listener = captor.value
    InputManager DisplayManager
    CPU RAM

    View Slide

  50. @Test
    fun testAdd() {
    val captor = ArgumentCaptor.forClass(Listener::class.java)
    verify(input).registerInputListener(captor.capture())
    val listener = captor.value
    input.add(2, 2)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  51. @Test
    fun testAdd() {
    // Captor stuff
    input.add(2, 2)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  52. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    verify(cpu).setRam(ram)
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    listener.onNewInput("+")
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a

    View Slide

  53. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    verify(cpu).setRam(ram)
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    listener.onNewInput("+")
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a

    View Slide

  54. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    verify(cpu).setRam(ram)
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    listener.onNewInput("+")
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a

    View Slide

  55. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    verify(cpu).setRam(ram)
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    listener.onNewInput("+")
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a

    View Slide

  56. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    verify(cpu).setRam(ram)
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    listener.onNewInput("+")
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a

    View Slide

  57. @Test
    fun testAdd() {
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  58. @Test
    fun testAdd() {
    verify(cpu).calculateSum(2, 2) // Actually 0
    verify(display).displayResult(4)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  59. @Test
    fun testAdd() {
    when(cpu.calculateSum(2, 2)).thenReturn(4)
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  60. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    verify(cpu).setRam(ram)
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    listener.onNewInput("+")
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    when(cpu.calculateSum(2, 2)).thenReturn(4)
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)

    View Slide

  61. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    verify(cpu).setRam(ram)
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    listener.onNewInput("+")
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    when(cpu.calculateSum(2, 2)).thenReturn(4)
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)

    View Slide

  62. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    verify(cpu).setRam(ram)
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    listener.onNewInput("+")
    verify(input).onInputReceived("+")
    verify(ram).storeInRAM(2)
    verify(cpu).loadInputToCpu("+")
    listener.onNewInput(2)
    verify(input).onInputReceived(2)
    verify(cpu).loadInputToCpu(2)
    verify(ram).loadFromRam()
    when(cpu.calculateSum(2, 2)).thenReturn(4)
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)

    View Slide

  63. @Test
    fun testAdd() {
    // Mocks...
    val calculator = Calculator(cpu, ram, display, input)
    // Captor stuff...
    input.add(2, 2)
    listener.onNewInput(2)
    listener.onNewInput("+")
    listener.onNewInput(2)
    when(cpu.calculateSum(2, 2)).thenReturn(4)
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)
    }a
    InputManager DisplayManager
    CPU RAM

    View Slide

  64. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    val calculator = Calculator(cpu, ram, display, input)
    }a

    View Slide

  65. InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    val calculator = Calculator(cpu, ram, display, input)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  66. InputManager DisplayManager
    CPU RAM
    InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    val calculator = Calculator(cpu, ram, display, input)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  67. DisplayManager
    CPU RAM
    InputManager DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    val calculator = Calculator(cpu, ram, display, input)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  68. DisplayManager
    CPU RAM DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    val calculator = Calculator(cpu, ram, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  69. DisplayManager
    RAM DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    val calculator = Calculator(cpu, ram, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  70. DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    val calculator = Calculator(CPU(), ram, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  71. @Test
    fun testAdd() {
    val calculator = Calculator(CPU(), ram, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a
    DisplayManager
    CPU RAM DisplayManager
    CPU RAM

    View Slide

  72. DisplayManager
    CPU DisplayManager
    CPU RAM
    @Test
    fun testAdd() {
    val calculator = Calculator(CPU(), ram, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  73. RAM

    View Slide

  74. RAM
    interface RAMa{
    afunastoreInRAM(key:aString,avalue:aT)
    afunaloadFromRam(key:aString):aT?
    }

    View Slide

  75. class InMemoryRAM : RAMa{
    private val store = mutableMapOf()
    override afunastoreInRAM(key:aString,avalue:aT) {
    return store[key] = value
    }
    override afunaloadFromRam(key:aString):aT? {
    return store[key]
    }
    }

    View Slide

  76. InMemoryRAM

    View Slide

  77. DisplayManager
    CPU InMemoryRAM
    @Test
    fun testAdd() {
    val ram = mock(RAM::class.java)
    val calculator = Calculator(CPU(), ram, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  78. DisplayManager
    CPU InMemoryRAM
    @Test
    fun testAdd() {
    val ram = InMemoryRAM()
    val calculator = Calculator(CPU(), ram, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  79. CPU DisplayManager
    CPU
    @Test
    fun testAdd() {
    val ram = InMemoryRAM()
    val calculator = Calculator(CPU(), ram, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  80. CPU DisplayManager
    CPU
    @Test
    fun testAdd() {
    val cpu = CPU(InMemoryRAM())
    val calculator = Calculator(cpu, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  81. CPU
    CPU DisplayManager
    @Test
    fun testAdd() {
    val cpu = CPU(InMemoryRAM())
    val calculator = Calculator(cpu, display)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a
    DisplayManager

    View Slide

  82. CPU
    CPU DisplayManager
    verify(display).displayResult(4)
    DisplayManager

    View Slide

  83. CPU
    CPU DisplayManager
    verify(display).displayResult(4)
    assertThat(display.getCurrentResult()).isEqualTo(4)
    DisplayManager

    View Slide

  84. CPU
    CPU DisplayManager
    verify(display).displayResult(4)
    assertThat(display.getCurrentResult()).isEqualTo(4)
    assertThat(display.recordedResults[0]).isEqualTo(4)
    DisplayManager

    View Slide

  85. CPU
    CPU
    val resultHandler = RecordingResultHandler()
    // Calc
    assertThat(resultHandler.results[0]).isEqualTo(4)
    ResultHandler

    View Slide

  86. CPU ResultHandler
    CPU
    @Test
    fun testAdd() {
    val cpu = CPU(InMemoryRAM())
    val resultHandler = RecordingResultHandler()
    val calculator = Calculator(cpu, resultHandler)
    calculator.add(2, 2)
    assertThat(resultHandler.results[0]).isEqualTo(4)
    }a

    View Slide

  87. CPU
    @Test
    fun testAdd() {
    val cpu = CPU(InMemoryRAM())
    val calculator = Calculator(cpu)
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  88. val cpu = CPU(InMemoryRAM())
    val calculator = Calculator(cpu)
    @Test
    fun testAdd() {
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  89. val cpu = CPU(InMemoryRAM())
    val calculator = Calculator(cpu)
    @Testa
    fun testAdd() {
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a
    @Testb
    fun testSub() {
    assertThat(calculator.subtract(2, 2)).isEqualTo(0)
    }b
    @Testc
    fun testMultiply() {
    assertThat(calculator.multiply(2, 2)).isEqualTo(4)
    }c

    View Slide

  90. val cpu = CPU(InMemoryRAM())
    val calculator = Calculator(cpu)
    @Testa
    fun testAdd() {
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    }a

    View Slide

  91. val cpu = CPU(InMemoryRAM())
    val resultHandler = RecordingResultHandler()
    val calculator = Calculator(cpu, resultHandler)
    @Test
    fun testAdd() {
    calculator.add(2, 2)
    calculator.add(2, 3)
    calculator.add(2, -1)
    assertThat(resultHandler.results)
    .containsExactly(4, 5, 1)
    }a

    View Slide

  92. Failure

    View Slide

  93. @Test
    fun foo() {
    try {
    foo.bar()
    } catch (e: Exception) {
    // Expected
    }
    }a

    View Slide

  94. @Test(expected = Exception::class)
    fun foo() {
    foo.bar()
    }a

    View Slide

  95. @Test(expected = ExpectedException::class)
    fun foo() {
    foo.bar()
    }a

    View Slide

  96. @Test()
    fun foo() {
    try {
    foo.bar()
    } catch (e: ExpectedException) {
    assertThat(e)
    .hasMessageThat()
    .contains("Oh no")
    }b
    }a

    View Slide

  97. @Test()
    fun foo() {
    try {
    foo.bar()
    throw AssertionError("No")
    } catch (e: ExpectedException) {
    assertThat(e)
    .hasMessageThat()
    .contains("Oh no")
    }b
    }a

    View Slide

  98. RxJava

    View Slide

  99. @Test()
    fun rx() {
    Observable.error()
    .subscribe()
    }a

    View Slide

  100. @Test()
    fun rx() {
    Observable.error()
    .subscribe()
    }a

    View Slide

  101. throw error

    View Slide

  102. // throw error
    Thread currentThread = Thread.currentThread();
    UncaughtExceptionHandler handler
    = currentThread.getUncaughtExceptionHandler();
    handler.uncaughtException(currentThread, error);

    View Slide

  103. // throw error
    Thread currentThread = Thread.currentThread();
    UncaughtExceptionHandler handler
    = currentThread.getUncaughtExceptionHandler();
    handler.uncaughtException(currentThread, error);
    https://github.com/uber/AutoDispose/blob/master/autodispose/src/test/java/com/uber/
    autodispose/RxErrorsRule.java

    View Slide

  104. TestObserver

    View Slide

  105. TestObserver
    Observable.just(1)
    .test()

    View Slide

  106. TestObserver
    Observable.just(1)
    .test()
    .assertValue(...)

    View Slide

  107. TestObserver
    Observable.just(1)
    .test()
    .assertValue(...)
    .assertError(...)

    View Slide

  108. TestObserver
    Observer o = mock(Observer::class.java)
    Observable.just(1)
    .subscribe(o)
    verify(o).onNext(...)
    verify(o).onError(...)

    View Slide

  109. TestObserver
    Observable.just(1)
    .test()
    .assertValue(...)
    .assertError(...)
    .awaitDone(...)

    View Slide

  110. Some others
    quickly because ⏰

    View Slide

  111. What about my
    architecture?

    View Slide

  112. class Calculator(val cpu: CPU,
    val resultHandler: ResultHandler,
    val inputManager: InputManager)

    View Slide

  113. class CalculatorPresenter(val cpu: CPU,
    val resultHandler: ResultHandler,
    val inputManager: InputManager)

    View Slide

  114. class CalculatorPresenter(val cpu: CPU,
    val resultHandler: ResultHandler,
    val inputManager: InputManager)
    Courtesy of Nick Butcher (@crafty)

    View Slide

  115. class CalculatorPresenter(val cpu: CPU,
    val resultHandler: ResultHandler,
    val inputManager: InputManager)
    0.0
    7 8 9
    4 5 6
    1 2 3
    C 0 ⏎

    View Slide

  116. class CurrentValueView : ResultHandler
    0.0
    7 8 9
    4 5 6
    1 2 3
    C 0 ⏎

    View Slide

  117. class NumberInputManager : InputManager
    class CurrentValueView : ResultHandler
    0.0
    7 8 9
    4 5 6
    1 2 3
    C 0 ⏎

    View Slide

  118. class NumberInputManager : InputManager
    class CurrentValueView : ResultHandler
    0.0
    7 8 9
    4 5 6
    1 2 3
    C 0 ⏎
    class ImeInputManager : InputManager

    View Slide

  119. class NumberInputManager : InputManager
    class CurrentValueView : ResultHandler
    7 8 9
    4 5 6
    1 2 3
    C 0 ⏎
    0.0

    View Slide

  120. What about DI?

    View Slide

  121. class MainActivity : Activity {
    @Inject lateinit var foo: Foo
    @Inject lateinit var bar: Bar
    }

    View Slide

  122. @Test
    fun testActivity() {
    val activity = ...
    activity.foo = TestFoo()
    activity.bar = MockBar()
    }

    View Slide

  123. @Test
    fun testActivity() {
    val activity = MainActivityTestHelper.create(
    TestFoo(),
    MockBar()
    )a
    }b

    View Slide

  124. @Test
    fun testActivity() {
    val activity = MainActivityTestHelper.create(
    TestFoo(),
    MockBar()
    )a
    }b
    RIBS: https://github.com/uber/RIBs/blob/master/android/libraries/rib-
    compiler-test/src/main/java/com/uber/rib/compiler/
    InteractorTestGenerator.java
    Injection Helper: https://github.com/dinosaurwithakatana/injection-
    helper

    View Slide

  125. What about
    state?

    View Slide

  126. State machines!

    View Slide

  127. In short

    View Slide

  128. Value is the
    boundary

    View Slide

  129. Value is the
    boundary
    assertThat(calculator.add(2, 2)).isEqualTo(4)
    assertThat(resultHandler.results[0]).isEqualTo(4)
    Observable.just(1)
    .test()
    .assertValue(...)
    .assertError(...)
    .awaitDone(...)

    View Slide

  130. Fakes are worth
    writing

    View Slide

  131. Fakes are worth
    writing
    class InMemoryRAM : RAM {
    private val store = mutableMapOf()
    override fun storeInRAM(key: String, value: T) {
    return store[key] = value
    }
    override fun loadFromRam(key: String): T? {
    return store[key]
    }
    }

    View Slide

  132. Ship your fakes

    View Slide

  133. Ship your fakes
    fun test(): TestObserver
    TestScopeProvider
    Room.inMemoryDatabaseBuilder(...)

    View Slide

  134. Tests should test
    the public API

    View Slide

  135. Tests should test
    the public API
    Semver your
    tests!

    View Slide

  136. Test are prod
    code

    View Slide

  137. Failures should
    be designed for

    View Slide

  138. Failures should
    be designed for
    throw AssertionError("Should have crashed before this")
    catch (e: ExpectedException) {
    assertThat(e).isWhatWeExpected()
    }
    @Rule val errorsRule = RxErrorsRule()

    View Slide

  139. More than one
    right way

    View Slide

  140. Keep it simple.

    View Slide

  141. Keep it pragmatic.

    View Slide

  142. Further reading
    • Boundaries: https://
    www.destroyallsoftware.com/talks/boundaries
    • Square libraries
    • RxJava
    • AutoDispose

    View Slide

  143. Fini, merci!
    Zac Sweers - Uber
    @pandanomic

    View Slide