Double Trouble

Preview:

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) { “tom@crui.se” }

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) { “tom@crui.se” }

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) { “tom@crui.se” }

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) { “tom@crui.se” }

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 on_tap@fridge.cold_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

Recommended