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

Double Trouble: Clarity on Test Doubles

PromptWorks
February 21, 2014

Double Trouble: Clarity on Test Doubles

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

PromptWorks

February 21, 2014
Tweet

More Decks by PromptWorks

Other Decks in Programming

Transcript

  1. 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
  2. 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 }
  3. 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 }
  4. 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 }
  5. 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 }
  6. TEST DOUBLE PATTERNS • Dummy Object • Fake Object •

    Mock Object • Test Stub • Test Spy
  7. 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 }
  8. 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 }
  9. 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
  10. NO DOUBLES let(:email) { “[email protected]” } ! subject { User.create(email:

    email) } ! it { should be_persisted } its(:username) { should eq email }
  11. 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) { “[email protected]” } ! 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 }
  12. TEST STUB An object which replaces the real DOC to

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

    indirect output from the SUT let(:spy_queue) { double(push: true) } let(:email) { “[email protected]” } ! 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
  14. 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
  15. 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
  16. 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
  17. class Buddy ! attr_reader :supermarket ! ! ! ! def

    make_breakfast(request=“Steak & eggs”) ingredients = supermarket.find(request) prepare(ingredients) end end ! ! !
  18. 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
  19. 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 ! ! !
  20. “MOCKIST” TDD / BDD • Uses mocks for all DOCs

    • Likes the test writing process to inform design decisions • Tests in strict isolation
  21. 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
  22. 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
  23. 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
  24. 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