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

Double Trouble: Clarity on Test Doubles

99e2a6afab542ba98a9f1d1cae6c9670?s=47 PromptWorks
February 21, 2014

Double Trouble: Clarity on Test Doubles

Presented by Greg Sterndale. https://www.promptworks.com

99e2a6afab542ba98a9f1d1cae6c9670?s=128

PromptWorks

February 21, 2014
Tweet

Transcript

  1. DOUBLE TROUBLE Clarity on test doubles.

  2. TESTING PHASES exercise verify teardown let(:sox) { Team.new “Boston” }

    let(:drays) { Team.new “Tampa” } before { sox.play(drays) } ! it “should set standings” do expect(drays).to be_losing end ! after { standings.delete_all } setup
  3. TESTING PHASES setup exercise verify teardown → let(:sox) { Team.new

    “Boston” } let(:drays) { Team.new “Tampa” } before { sox.play(drays) } ! it “should set standings” do expect(drays).to be_losing end ! after { standings.delete_all }
  4. TESTING PHASES excercise setup verify teardown → let(:sox) { Team.new

    “Boston” } let(:drays) { Team.new “Tampa” } before { sox.play(drays) } ! it “should set standings” do expect(drays).to be_losing end ! after { standings.delete_all }
  5. TESTING PHASES verify setup exercise teardown → let(:sox) { Team.new

    “Boston” } let(:drays) { Team.new “Tampa” } before { sox.play(drays) } ! it “should set standings” do expect(drays).to be_losing end ! after { standings.delete_all }
  6. TESTING PHASES teardown setup exercise verify → let(:sox) { Team.new

    “Boston” } let(:drays) { Team.new “Tampa” } before { sox.play(drays) } ! it “should set standings” do expect(drays).to be_losing end ! after { standings.delete_all }
  7. DOC Test Double WHAT IS A TEST DOUBLE? SUT Indirect

    Output Indirect Input
  8. TEST DOUBLE PATTERNS • Dummy Object • Fake Object •

    Mock Object • Test Stub • Test Spy
  9. DUMMY OBJECT A placeholder that is passed to the SUT

    and never used let(:side_a) { 1 } let(:side_b) { 2 } let(:dummy) { Object.new } ! subject { HighSchoolTrig.hypotenuse(a, b, dummy) } ! it { should eq 2.236 }
  10. FAKE OBJECT An object which replaces the real DOC with

    an alternate implementation of the same functionality class FakePiCalc def pi; 3.14159; end end ! let(:radius) { 2 } ! before { MyGeo.pi_calculator = FakePiCalc } ! subject { MyGeo.circumference(radius) } ! it { should eq 13 }
  11. MOCKS, STUBS & SPIES An example: class User < ActiveRecord::Base

    ! before_create :enqueue_welcome_message ! def enqueue_welcome_message queue = Application.config.email_queue raise(“Failed to queue”) unless queue.push(email, “Welcome”) end ! end
  12. NO DOUBLES let(:email) { “tom@crui.se” } ! subject { User.create(email:

    email) } ! it { should be_persisted } its(:username) { should eq email }
  13. MOCK OBJECT An object which replaces the real DOC that

    can verify indirect output from the SUT with expectations let(:mock_queue) { double() } let(:email) { “tom@crui.se” } ! before do Application.config.email_queue = mock_queue expect(mock_queue).to receive(:push).with(email, “Welcome”) end subject { User.create(email: email) } ! it { should be_persisted } its(:username) { should eq email }
  14. TEST STUB An object which replaces the real DOC to

    control indirect input to the SUT let(:stub_queue) { double(push: true) } let(:email) { “tom@crui.se” } ! before do Application.config.email_queue = stub_queue end ! subject { User.create(email: email) } ! it { should be_persisted } its(:username) { should eq email }
  15. TEST SPY A more capable Test Stub allowing verification of

    indirect output from the SUT let(:spy_queue) { double(push: true) } let(:email) { “tom@crui.se” } ! before do Application.config.email_queue = spy_queue end ! subject { User.create(email: email) } ! it { should be_persisted } its(:username) { should eq email } it “should enqueue welcome message” do expect(spy_queue).to have_received(:push).with(email, “Ohai”) end
  16. DESIGNING FOR DOUBLES • Dependency Lookup • Dependency Injection

  17. class Buddy def good_friend?; on_tap.craft?; end ! def on_tap Fridge.cold_one

    end end ! describe Buddy, “serving coors” do # TODO control indirect input to the SUT it “should not be a good friend” do expect(subject).not_to be_good_friend end
  18. DEPENDENCY LOOKUP class Buddy def good_friend?; on_tap.craft?; end ! def

    on_tap Fridge.cold_one end end ! describe Buddy, “serving coors” do let(:coors) { double(craft?: false) } ! before { Fridge.stubs(:cold_one) { coors } } it “should not be a good friend” do expect(subject).not_to be_good_friend end
  19. class Buddy attr_accessor :fridge def good_friend?; on_tap.craft?; end ! def

    on_tap @fridge.cold_one end end ! describe Buddy, “serving coors” do let(:coors) { double(craft?: false) } let(:stub_fridge) { double(cold_one: coors) } before { subject.fridge = stub_fridge } it “should not be a good friend” do expect(subject).not_to be_good_friend end DEPENDENCY INJECTION
  20. RETROFITTING • Test-Specific Subclasses • Test Hooks

  21. class Buddy ! attr_reader :supermarket ! ! ! ! def

    make_breakfast(request=“Steak & eggs”) ingredients = supermarket.find(request) prepare(ingredients) end end ! ! !
  22. TEST-SPECIFIC SUBCLASSES class Buddy ! attr_reader :supermarket ! ! !

    ! def make_breakfast(request=“Steak & eggs”) ingredients = supermarket.find(request) prepare(ingredients) end end ! class TestBuddy < Buddy attr_writer :supermarket end
  23. TEST HOOKS class Buddy if ENV != “TEST” attr_reader :supermarket

    else attr_accessor :supermarket end ! def make_breakfast(request=“Steak & eggs”) ingredients = supermarket.find(request) prepare(ingredients) end end ! ! !
  24. ARE THEY FOR YOU?

  25. “MOCKIST” TDD / BDD • Uses mocks for all DOCs

    • Likes the test writing process to inform design decisions • Tests in strict isolation
  26. CLASSIC TDD • Uses test doubles only for awkward DOCs,

    favoring “real” objects • Minimizes coupling between tests and implementation • Tests small clusters of components, not isolated units
  27. CLASSIC TDDERS CONSIDER USING A TEST DOUBLE IF: • The

    behavior of the DOC cannot be changed/observed • Use of the DOC could cause unwanted side-effects • The DOC is too slow • The DOC doesn’t exist yet
  28. OVERUSE CAN LEAD TO: • Over specified tests of the

    SUT’s process, not its result • Fragile tests that break when implementation changes • Untested integration • Less time on Hacker News while your build runs
  29. MORE xUnit Test Patterns: xunitpatterns.com ! Mocks aren’t Stubs by

    Martin Fowler: martinfowler.com/articles/mocksArentStubs.html ! A case against a case against mocking and stubbing by David Chelimsky: blog.davidchelimsky.net/2008/12/11/a-case-against-a-case-against-mocking-and-stubbing/ ! Timecop for testing time-dependent code: github.com/travisjeffery/timecop ! RSpec: rspec.info ! MiniTest: ruby-doc.org/stdlib ! Mocha: github.com/freerange/mocha
  30. greg@promptworks.com