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 full-size slide

  2. Pragmatic
    Testing

    View full-size slide

  3. Pragmatic?
    Testing

    View full-size slide

  4. Non-Goals
    • Convince you testing is worthwhile

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. InputManager DisplayManager
    CPU RAM

    View full-size slide

  14. InputManager DisplayManager
    CPU RAM

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. @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 full-size slide

  20. @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 full-size slide

  21. @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 full-size slide

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

    View full-size slide

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

    View full-size slide

  24. @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 full-size slide

  25. @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 full-size slide

  26. @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 full-size slide

  27. @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 full-size slide

  28. @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 full-size slide

  29. @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 full-size slide

  30. @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 full-size slide

  31. 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 full-size slide

  32. 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 full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. @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 full-size slide

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

    View full-size slide

  43. 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 full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

  46. 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 full-size slide

  47. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  50. @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 full-size slide

  51. 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 full-size 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()
    when(cpu.calculateSum(2, 2)).thenReturn(4)
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)

    View full-size 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()
    when(cpu.calculateSum(2, 2)).thenReturn(4)
    verify(cpu).calculateSum(2, 2)
    verify(display).displayResult(4)

    View full-size slide

  54. @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 full-size slide

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

    View full-size slide

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

    View full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  65. 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 full-size slide

  66. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  70. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  75. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  78. 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 full-size slide

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

    View full-size slide

  80. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  89. // 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 full-size slide

  90. TestObserver

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  96. Some others
    quickly because ⏰

    View full-size slide

  97. What about my
    architecture?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  101. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  106. What about DI?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  110. @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 full-size slide

  111. What about
    state?

    View full-size slide

  112. State machines!

    View full-size slide

  113. Value is the
    boundary

    View full-size slide

  114. 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 full-size slide

  115. Fakes are worth
    writing

    View full-size slide

  116. 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 full-size slide

  117. Ship your fakes

    View full-size slide

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

    View full-size slide

  119. Tests should test
    the public API

    View full-size slide

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

    View full-size slide

  121. Test are prod
    code

    View full-size slide

  122. Failures should
    be designed for

    View full-size slide

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

    View full-size slide

  124. More than one
    right way

    View full-size slide

  125. Keep it simple.

    View full-size slide

  126. Keep it pragmatic.

    View full-size slide

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

    View full-size slide

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

    View full-size slide