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

Test Doubles are Not To Be Mocked

Noel Rappin
November 11, 2016

Test Doubles are Not To Be Mocked

Test doubles (which you may know under the alias “mock objects”) are the most misunderstood and misused testing tools we've got. Starting with the fact that nobody can agree on what to call them. Contrary to what you may have heard, test doubles do not inherently lead to fragile tests. What they do is hold up a harsh mirror to the assumptions in our code. They are the light saber of testing tools: a more elegant weapon for a more civilized age. But be careful not to cut your code in half, so to speak. Herein: a guide to using test doubles without losing your sanity.

Noel Rappin

November 11, 2016
Tweet

More Decks by Noel Rappin

Other Decks in Technology

Transcript

  1. This is a Mock Slide Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  2. Hi! Noel Rappin | Test Doubles are not to be

    Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  3. Noel Rappin | Test Doubles are not to be Mocked

    | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  4. This is the talk about test doubles Noel Rappin |

    Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  5. Not the talk by the head of Test Double Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  6. Justin isn't here, so I can mock him... Noel Rappin

    | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  7. expect(:justin).to recieve(:this_talk) .and_return(:polite_dissent) Noel Rappin | Test Doubles are not

    to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  8. So, I wrote a book Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  9. And I wrote some tests Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  10. it "calculates price where tickets have discounts" do ticket_one =

    spy(price_check: 1000) ticket_two = spy(price_check: 1000) discount = spy() calculator = PriceCalculator.new([ticket_one, ticket_two], [discount]) calculator.calculate! expect(calculator.price_cents.to eq(2000)) expect(ticket_one).to have_received(:price_check).with(discount) expect(ticket_two).to have_received(:price_check).with(discount) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  11. Then I un-wrote some tests Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  12. it "calculates price where tickets have discounts" do ticket_one =

    Ticket.create(price_cents: 2000) ticket_two = Ticket.create(price_cents: 2000) discount = DiscountCode.create(discount_percentage: 25) calculator = PriceCalculator.new([ticket_one, ticket_two], [discount]) calculator.calculate! expect(calculator.price_cents.to eq(3000)) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  13. Why did I do that? Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  14. I've been ambivalent about test doubles for years Noel Rappin

    | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  15. "I have to say, as much as I love using

    mocks and stubs to cover hard-to-reach objects and states, my own history with very strict behavior-based mock test structures hasn’t been great. My experience was that writing all the mocks around a given object tended to be a drag on the test process. But I’m wide open to the possibility that this method works better for others or that I’m not doing it right." Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  16. Three years later Noel Rappin | Test Doubles are not

    to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  17. "My opinion about the best way to use mock objects

    changes every few months. I’ll try some mocks, they’ll work well, I’ll start using more mocks, they’ll start getting in the way, I’ll back off, and then I’ll think, “Let’s try some mocks.” This cycle has been going for years and I have no reason to think it’s going to change anytime soon." Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  18. Why does this keep happening to me? Noel Rappin |

    Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  19. What can I do about it? Noel Rappin | Test

    Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  20. Test Doubles are not to be Mocked Noel Rappin, Table

    XI (@noelrap) Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  21. I'm not talking about this: Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  22. it "handles a stripe failure" do token = instance_double(StripeCharge, success:

    false) allow(StripeCharge).to receive(:create).and return(token) workflow.run expect(workflow.status).to eq(:failure) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  23. Non controversial usage replacing heavyweight objects specifying failure states Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  24. What makes a test "good"? Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  25. SWIFT Straightforward, Well-Defined, Independent, Fast, Truthful Noel Rappin | Test

    Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  26. Long term priorities might be different Noel Rappin | Test

    Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  27. A good test Leads to well-designed code Noel Rappin |

    Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  28. What is "well-designed code"? Noel Rappin | Test Doubles are

    not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  29. Clear Easy to change Noel Rappin | Test Doubles are

    not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  30. How? Noel Rappin | Test Doubles are not to be

    Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  31. Domain discovery Behavior validation Safety net Noel Rappin | Test

    Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  32. A Tale of Two Tests Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  33. Calculate the total price of multiple tickets, given a discount

    Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  34. State test it "calculates price where tickets have discounts" do

    ticket_one = Ticket.create(price_cents: 2000) ticket_two = Ticket.create(price_cents: 2000) discount = DiscountCode.create(discount_percentage: 25) calculator = PriceCalculator.new([ticket_one, ticket_two], [discount]) calculator.calculate! expect(calculator.price_cents.to eq(3000)) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  35. Spy Test (!) it "calculates price where tickets have discounts"

    do ticket_one = spy(price_check: 1000) ticket_two = spy(price_check: 1000) discount = spy() calculator = PriceCalculator.new([ticket_one, ticket_two], [discount]) calculator.calculate! expect(calculator.price_cents.to eq(2000)) expect(ticket_one).to have_received(:price_check).with(discount) expect(ticket_two).to have_received(:price_check).with(discount) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  36. State: uses real objects, expectation on state Spy !: uses

    test doubles, expectations on behavior Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  37. Straightforward: State Noel Rappin | Test Doubles are not to

    be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  38. Well-defined Tie Noel Rappin | Test Doubles are not to

    be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  39. Independent Spy Noel Rappin | Test Doubles are not to

    be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  40. Fast Spy Noel Rappin | Test Doubles are not to

    be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  41. Truthful Let's examine... Noel Rappin | Test Doubles are not

    to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  42. If the Calculator has a bug: State fails Spy fails

    Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  43. If the Ticket has a bug State fails Spy passes

    Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  44. If the Ticket doesn't exist State fails Spy passes Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  45. So far, this seems bad for the Spy test Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  46. The code can be wrong and the double test will

    pass Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  47. This is a matter of perspective Noel Rappin | Test

    Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  48. And this is not the only test in the suite

    Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  49. Let's say the discount logic gets more complex Noel Rappin

    | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  50. If the ticket API changes: Team State fails Team Double

    passes Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  51. The state test fails even though neither the code or

    the test changed Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  52. The test can fail even though the test is right

    Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  53. Design goal: A failure state causes exactly one test to

    fail Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  54. Doubles test the design and the behavior Noel Rappin |

    Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  55. The double encourages the creation of additional tests Noel Rappin

    | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  56. This is good because Noel Rappin | Test Doubles are

    not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  57. It encourages good design practices Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  58. This is bad because Noel Rappin | Test Doubles are

    not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  59. The design might change Noel Rappin | Test Doubles are

    not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  60. It is hard to drive the next failing test Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  61. This implies a lot of test and code isolation Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  62. More isolation that many people are comfortable committing to up

    front Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  63. Test doubles are design canaries Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  64. Lot of setup might mean poorly factored design Noel Rappin

    | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  65. This level of isolation is hard on a team if

    you are the only one who cares Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  66. A third-party framework can make this hard Noel Rappin |

    Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  67. So what happened? Noel Rappin | Test Doubles are not

    to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  68. I wrote the spy tests Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  69. I was unwilling or unable to make the code isolated

    enough to keep the spies Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  70. I re-wrote them Noel Rappin | Test Doubles are not

    to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  71. This is easy But it breaks encapsulation But it's easy

    Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  72. Takeaways Noel Rappin | Test Doubles are not to be

    Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  73. Conway's law applies to tests Noel Rappin | Test Doubles

    are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  74. Think about what will make your tests fail Noel Rappin

    | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  75. Try doubles where behavior is more important than state Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  76. Try writing Rails tests with isolation Noel Rappin | Test

    Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  77. Using an integration test can force additional unit tests Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  78. Noel Rappin (@noelrap) (http://www.noelrappin.com) Table XI https://tinyletter.com/noelrap http://pragprog.com/book/nrwebpay http://pragprog.com/book/nrtest2 Noel

    Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap
  79. Noel Rappin | Test Doubles are not to be Mocked

    | http://www.noelrappin.com | http://www.tablexi.com | @noelrap