Double Trouble

1,443
-1

Published on

Clarity on Test Doubles in Ruby
Mocks, Stubs, Fakes, Dummies, Spies and such.

Presented at Boston.rb, Philly.rb and NYC.rb.

Published in: Technology, Business
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,443
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
22
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Double Trouble

  1. 1. DOUBLETROUBLE Clarity on test doubles.
  2. 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. 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. 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. 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. 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. 7. WHAT IS ATEST DOUBLE?
  8. 8. WHAT IS ATEST DOUBLE? SUT
  9. 9. DOC WHAT IS ATEST DOUBLE? SUT
  10. 10. DOC WHAT IS ATEST DOUBLE? SUT Indirect Output
  11. 11. DOC WHAT IS ATEST DOUBLE? SUT Indirect Output Indirect Input
  12. 12. DOCTest Double WHAT IS ATEST DOUBLE? SUT Indirect Output Indirect Input
  13. 13. TEST DOUBLE PATTERNS • Dummy Object • Fake Object • Mock Object • Test Stub • Test Spy
  14. 14. 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 }
  15. 15. 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 }
  16. 16. 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
  17. 17. NO DOUBLES let(:email) { “tom@crui.se” } subject { User.create(email: email) } it { should be_persisted } its(:username) { should eq email }
  18. 18. 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 }
  19. 19. 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 }
  20. 20. TEST SPY A more capableTest 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
  21. 21. DESIGNING FOR DOUBLES • Dependency Lookup • Dependency Injection
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. RETROFITTING • Test-Specific Subclasses • Test Hooks
  26. 26. class Buddy attr_reader :supermarket def make_breakfast(request=“Steak & eggs”) ingredients = supermarket.find(request) prepare(ingredients) end end
  27. 27. 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
  28. 28. 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
  29. 29. ARETHEY FORYOU?
  30. 30. “MOCKIST”TDD / BDD • Uses mocks for all DOCs • Likes the test writing process to inform design decisions • Tests in strict isolation
  31. 31. CLASSICTDD • Uses test doubles only for awkward DOCs, favoring “real” objects • Minimizes coupling between tests and implementation • Tests small clusters of components, not isolated units
  32. 32. CLASSICTDDERS CONSIDER USING ATEST 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
  33. 33. OVERUSE CAN LEADTO: • 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
  34. 34. 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
  35. 35. greg@promptworks.com
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×