• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Double Trouble
 

Double Trouble

on

  • 2,158 views

Clarity on Test Doubles in Ruby

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

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

Statistics

Views

Total Views
2,158
Views on SlideShare
2,136
Embed Views
22

Actions

Likes
3
Downloads
19
Comments
0

3 Embeds 22

http://www.slideshare.net 11
http://www.linkedin.com 10
https://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Double Trouble Double Trouble Presentation Transcript

    • DOUBLETROUBLE Clarity 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_losing end 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_losing end 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_losing end 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_losing end 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_losing end after { standings.delete_all }
    • WHAT IS ATEST DOUBLE?
    • WHAT IS ATEST DOUBLE? SUT
    • DOC WHAT IS ATEST DOUBLE? SUT
    • DOC WHAT IS ATEST DOUBLE? SUT Indirect Output
    • DOC WHAT IS ATEST DOUBLE? SUT Indirect Output Indirect Input
    • DOCTest Double WHAT IS ATEST 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 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 }
    • 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 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 }
    • 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 }
    • 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
    • DESIGNING FOR DOUBLES • Dependency Lookup • Dependency Injection
    • 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
    • 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
    • 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
    • RETROFITTING • Test-Specific Subclasses • Test Hooks
    • class Buddy attr_reader :supermarket def make_breakfast(request=“Steak & eggs”) ingredients = supermarket.find(request) prepare(ingredients) end end
    • 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
    • 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
    • ARETHEY FORYOU?
    • “MOCKIST”TDD / BDD • Uses mocks for all DOCs • Likes the test writing process to inform design decisions • Tests in strict isolation
    • 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
    • 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
    • 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
    • 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
    • greg@promptworks.com