Download pdf - Double Trouble

Transcript
Page 1: Double Trouble

DOUBLE TROUBLEClarity on test doubles.

Page 2: Double Trouble

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

Page 3: Double Trouble

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 }

Page 4: Double Trouble

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 }

Page 5: Double Trouble

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 }

Page 6: Double Trouble

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 }

Page 7: Double Trouble

WHAT IS A TEST DOUBLE?

Page 8: Double Trouble

WHAT IS A TEST DOUBLE?

SUT

Page 9: Double Trouble

DOC

WHAT IS A TEST DOUBLE?

SUT

Page 10: Double Trouble

DOC

WHAT IS A TEST DOUBLE?

SUT

Indirect Output

Page 11: Double Trouble

DOC

WHAT IS A TEST DOUBLE?

SUT

Indirect Output

Indirect Input

Page 12: Double Trouble

DOCTestDouble

WHAT IS A TEST DOUBLE?

SUT

Indirect Output

Indirect Input

Page 13: Double Trouble

TEST DOUBLE PATTERNS

•Dummy Object

• Fake Object

•Mock Object

• Test Stub

• Test Spy

Page 14: Double Trouble

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 }

Page 15: Double Trouble

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 }

Page 16: Double Trouble

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

Page 17: Double Trouble

NO DOUBLES

let(:email) { “[email protected]” }

subject { User.create(email: email) }

it { should be_persisted }its(:username) { should eq email }

Page 18: Double Trouble

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 }

Page 19: Double Trouble

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 }

Page 20: Double Trouble

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

Page 21: Double Trouble

DESIGNING FOR DOUBLES

•Dependency Lookup

•Dependency Injection

Page 22: Double Trouble

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

Page 23: Double Trouble

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

Page 24: Double Trouble

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

Page 25: Double Trouble

RETROFITTING

• Test-Specific Subclasses

• Test Hooks

Page 26: Double Trouble

class Buddy

attr_reader :supermarket

def make_breakfast(request=“Steak & eggs”)ingredients = supermarket.find(request)prepare(ingredients)

endend

Page 27: Double Trouble

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

Page 28: Double Trouble

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

Page 29: Double Trouble

ARE THEY FOR YOU?

Page 30: Double Trouble

“MOCKIST” TDD / BDD

• Uses mocks for all DOCs

• Likes the test writing process to inform design decisions

• Tests in strict isolation

Page 31: Double Trouble

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

Page 32: Double Trouble

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

Page 33: Double Trouble

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

Page 34: Double Trouble

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