Upload
gsterndale
View
1.309
Download
5
Tags:
Embed Size (px)
DESCRIPTION
Clarity on Test Doubles in Ruby Mocks, Stubs, Fakes, Dummies, Spies and such. Presented at Boston.rb, Philly.rb and NYC.rb.
Citation preview
DOUBLE TROUBLEClarity on test doubles.
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_losingend
after { standings.delete_all }
setup
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_losingend
after { standings.delete_all }
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_losingend
after { standings.delete_all }
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_losingend
after { standings.delete_all }
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_losingend
after { standings.delete_all }
WHAT IS A TEST DOUBLE?
WHAT IS A TEST DOUBLE?
SUT
DOC
WHAT IS A TEST DOUBLE?
SUT
DOC
WHAT IS A TEST DOUBLE?
SUT
Indirect Output
DOC
WHAT IS A TEST DOUBLE?
SUT
Indirect Output
Indirect Input
DOCTestDouble
WHAT IS A TEST DOUBLE?
SUT
Indirect Output
Indirect Input
TEST DOUBLE PATTERNS
•Dummy Object
• Fake Object
•Mock Object
• Test Stub
• Test Spy
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 }
FAKE OBJECT
An object which replaces the real DOC with an alternate implementation of the same functionalityclass FakePiCalcdef pi; 3.14159; end
end
let(:radius) { 2 }
before { MyGeo.pi_calculator = FakePiCalc }
subject { MyGeo.circumference(radius) }
it { should eq 13 }
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
NO DOUBLES
let(:email) { “[email protected]” }
subject { User.create(email: email) }
it { should be_persisted }its(:username) { should eq email }
MOCK OBJECTAn object which replaces the real DOC that can verify indirect output from the SUT with expectationslet(:mock_queue) { double() }let(:email) { “[email protected]” }
before do Application.config.email_queue = mock_queueexpect(mock_queue).to receive(:push).with(email, “Welcome”)
endsubject { User.create(email: email) }
it { should be_persisted }its(:username) { should eq email }
TEST STUBAn object which replaces the real DOC to control indirect input to the SUTlet(:stub_queue) { double(push: true) }let(:email) { “[email protected]” }
before do Application.config.email_queue = stub_queueend
subject { User.create(email: email) }
it { should be_persisted }its(:username) { should eq email }
TEST SPYA more capable Test Stub allowing verification of indirect output from the SUTlet(:spy_queue) { double(push: true) }let(:email) { “[email protected]” }
before do Application.config.email_queue = spy_queueend
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
DESIGNING FOR DOUBLES
•Dependency Lookup
•Dependency Injection
class Buddy def good_friend?; on_tap.craft?; end
def on_tapFridge.cold_one
endend
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
DEPENDENCY LOOKUPclass Buddy def good_friend?; on_tap.craft?; end
def on_tapFridge.cold_one
endend
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
class Buddyattr_accessor :fridge def good_friend?; on_tap.craft?; end
def [email protected]_one
endend
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
RETROFITTING
• Test-Specific Subclasses
• Test Hooks
class Buddy
attr_reader :supermarket
def make_breakfast(request=“Steak & eggs”)ingredients = supermarket.find(request)prepare(ingredients)
endend
TEST-SPECIFIC SUBCLASSESclass Buddy
attr_reader :supermarket
def make_breakfast(request=“Steak & eggs”)ingredients = supermarket.find(request)prepare(ingredients)
endend
class TestBuddy < Buddyattr_writer :supermarket
end
TEST HOOKSclass Buddyif ENV != “TEST”attr_reader :supermarket
elseattr_accessor :supermarket
end
def make_breakfast(request=“Steak & eggs”)ingredients = supermarket.find(request)prepare(ingredients)
endend
ARE THEY FOR YOU?
“MOCKIST” TDD / BDD
• Uses mocks for all DOCs
• Likes the test writing process to inform design decisions
• Tests in strict isolation
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
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
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
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