126
Don’t Mock Yourself Out David Chelimsky Articulated Man, Inc

Don T Mock Yourself Out Presentation

Embed Size (px)

Citation preview

Page 1: Don T Mock Yourself Out Presentation

Don’t MockYourself Out

David ChelimskyArticulated Man, Inc

Page 2: Don T Mock Yourself Out Presentation
Page 4: Don T Mock Yourself Out Presentation

Classical and Mockist Testing

Page 5: Don T Mock Yourself Out Presentation

Classical and Mockist Testing

Page 6: Don T Mock Yourself Out Presentation

Classical and Mockist Testing

Page 7: Don T Mock Yourself Out Presentation

classicist mockist

Page 8: Don T Mock Yourself Out Presentation

merbist railsist

Page 9: Don T Mock Yourself Out Presentation

rspecist testunitist

Page 10: Don T Mock Yourself Out Presentation
Page 11: Don T Mock Yourself Out Presentation

ist bin einred herring

Page 12: Don T Mock Yourself Out Presentation

The big issue here is when to use a

mock

http://martinfowler.com/articles/mocksArentStubs.html

Page 13: Don T Mock Yourself Out Presentation

agenda๏ overview of stubs and mocks

๏ mocks/stubs applied to rails

๏ guidelines and pitfalls

๏ questions

Page 14: Don T Mock Yourself Out Presentation

test double

Page 15: Don T Mock Yourself Out Presentation

test stubdescribe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

Page 16: Don T Mock Yourself Out Presentation

test stubdescribe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

Page 17: Don T Mock Yourself Out Presentation

test stubdescribe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

Page 18: Don T Mock Yourself Out Presentation

test stubdescribe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

Page 19: Don T Mock Yourself Out Presentation

mock objectdescribe Statement do it "logs a message when printed" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') logger = mock("logger") statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 20: Don T Mock Yourself Out Presentation

mock objectdescribe Statement do it "logs a message when printed" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') logger = mock("logger") statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 21: Don T Mock Yourself Out Presentation

mock objectdescribe Statement do it "logs a message when printed" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') logger = mock("logger") statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 22: Don T Mock Yourself Out Presentation

mock objectdescribe Statement do it "logs a message when printed" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') logger = mock("logger") statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 23: Don T Mock Yourself Out Presentation

mock objectdescribe Statement do it "logs a message when printed" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') logger = mock("logger") statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 24: Don T Mock Yourself Out Presentation

method level concepts

Page 25: Don T Mock Yourself Out Presentation

describe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 26: Don T Mock Yourself Out Presentation

describe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 27: Don T Mock Yourself Out Presentation

describe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 28: Don T Mock Yourself Out Presentation

method stubdescribe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 29: Don T Mock Yourself Out Presentation

describe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 30: Don T Mock Yourself Out Presentation

message expectationdescribe Statement do it "logs a message when printed" do customer = Object.new logger = Object.new customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 31: Don T Mock Yourself Out Presentation

things aren’t always as they seem

Page 32: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 33: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 34: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 35: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 36: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

messageexpectation

Page 37: Don T Mock Yourself Out Presentation

bound to implementation

describe Statement do it "uses the customer name in the header" do customer = mock("customer") statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer')

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 38: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 39: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 40: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 41: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

Page 42: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

????

Page 43: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

messageexpectation

Page 44: Don T Mock Yourself Out Presentation

describe Statement do it "uses the customer name in the header" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer)

statement.header.should == "Statement for Joe Customer" endend

class Statement def header "Statement for #{@customer.name}" endend

bound to implementation

Page 45: Don T Mock Yourself Out Presentation

stubs are often used like mocks

Page 46: Don T Mock Yourself Out Presentation

mocks are often used like stubs

Page 47: Don T Mock Yourself Out Presentation

we verify stubs by checking state after

an action

Page 48: Don T Mock Yourself Out Presentation

we tell mocks to verify interactions

Page 49: Don T Mock Yourself Out Presentation

sometimes stubs just make the

system run

Page 50: Don T Mock Yourself Out Presentation

when aremethod stubs

helpful?

Page 51: Don T Mock Yourself Out Presentation

isolation fromnon-determinism

Page 52: Don T Mock Yourself Out Presentation

random values

Page 53: Don T Mock Yourself Out Presentation

random valuesclass BoardTest < MiniTest::Unit::TestCase def test_allows_move_to_last_square board = Board.new( :squares => 50, :die => MiniTest::Mock.new.expect('roll', 2) ) piece = Piece.new

board.place(piece, 48) board.move(piece) assert board.squares[48].contains?(piece) endend

Page 54: Don T Mock Yourself Out Presentation

time

describe Event do it "is not happening before the start time" do now = Time.now start = now + 1 Time.stub(:now).and_return now event = Event.new(:start => start) event.should_not be_happening endend

Page 55: Don T Mock Yourself Out Presentation

isolation fromexternal dependencies

Page 56: Don T Mock Yourself Out Presentation

network access

Subject

DatabaseDatabase Interface

NetworkInterface Internets

Page 57: Don T Mock Yourself Out Presentation

network accessdef test_successful_purchase_sends_shipping_message ActiveMerchant::Billing::Base.mode = :test gateway = ActiveMerchant::Billing::TrustCommerceGateway.new( :login => 'TestMerchant', :password => 'password' ) item = stub() messenger = mock() messenger.expects(:ship).with(item) purchase = Purchase.new(gateway, item, credit_card, messenger) purchase.finalizeend

Page 58: Don T Mock Yourself Out Presentation

network access

Subject

StubDatabase

StubNetwork

CodeExample

Page 59: Don T Mock Yourself Out Presentation

network access

def test_successful_purchase_sends_shipping_message gateway = stub() gateway.stubs(:authorize).returns( ActiveMerchant::Billing::Response.new(true, "ignore") ) item = stub() messenger = mock()

messenger.expects(:ship).with(item) purchase = Purchase.new(gateway, item, credit_card, messenger) purchase.finalizeend

Page 60: Don T Mock Yourself Out Presentation

polymorphic collaborators

Page 61: Don T Mock Yourself Out Presentation

strategies

describe Employee do it "delegates pay() to payment strategy" do payment_strategy = mock() employee = Employee.new(payment_strategy)

payment_strategy.expects(:pay) employee.pay endend

Page 62: Don T Mock Yourself Out Presentation

mixins/pluginsdescribe AgeIdentifiable do describe "#can_vote?" do it "raises if including does not respond to birthdate" do object = Object.new object.extend AgeIdentifiable expect { object.can_vote? }.to raise_error( /must supply a birthdate/ ) end

it "returns true if birthdate == 18 years ago" do object = Object.new stub(object).birthdate {18.years.ago.to_date} object.extend AgeIdentifiable object.can_vote?.should be(true) end endend

Page 63: Don T Mock Yourself Out Presentation

when aremessage expectations

helpful?

Page 64: Don T Mock Yourself Out Presentation

side effectsdescribe Statement do it "logs a message when printed" do customer = stub("customer") customer.stub(:name).and_return('Joe Customer') logger = mock("logger") statement = Statement.new(customer, logger)

logger.should_receive(:log).with(/Joe Customer/) statement.print endend

Page 65: Don T Mock Yourself Out Presentation

cachingdescribe ZipCode do it "should only validate once" do validator = mock() zipcode = ZipCode.new("02134", validator) validator.should_receive(:valid?).with("02134").once. and_return(true) zipcode.valid? zipcode.valid? endend

Page 66: Don T Mock Yourself Out Presentation

interface discoverydescribe "thing I'm working on" do it "does something with some assistance" do thing_i_need = mock() thing_i_am_working_on = ThingIAmWorkingOn.new(thing_i_need)

thing_i_need.should_receive(:help_me).and_return('what I need') thing_i_am_working_on.do_something_complicated endend

Page 67: Don T Mock Yourself Out Presentation

isolation testing

Page 68: Don T Mock Yourself Out Presentation

specifying/testing individual

objects in isolation

Page 69: Don T Mock Yourself Out Presentation

good fit with lots of little objects

Page 70: Don T Mock Yourself Out Presentation
Page 71: Don T Mock Yourself Out Presentation

all of these concepts apply to

the non-rails specific parts of

our rails apps

Page 72: Don T Mock Yourself Out Presentation

isolation testing the rails-specific parts

of our applicationss

Page 73: Don T Mock Yourself Out Presentation
Page 74: Don T Mock Yourself Out Presentation

M

C

V

Page 75: Don T Mock Yourself Out Presentation

ViewController

Model

Page 76: Don T Mock Yourself Out Presentation

ViewController

Model

BrowserRouter

Database

Page 77: Don T Mock Yourself Out Presentation

rails testing๏ unit tests

๏ functional tests

๏ integration tests

Page 78: Don T Mock Yourself Out Presentation

rails unit tests๏ model classes (repositories)

๏ model objects

๏ database

Page 79: Don T Mock Yourself Out Presentation

๏ model classes (repositories)

๏ model objects

๏ database

๏ views

๏ controllers

rails functional tests

Page 80: Don T Mock Yourself Out Presentation

๏ model classes (repositories)

๏ model objects

๏ database

๏ views

๏ controllers

rails functional tests

Page 81: Don T Mock Yourself Out Presentation

๏ model classes (repositories)

๏ model objects

๏ database

๏ views

๏ controllers

rails functional tests

!DRY

Page 82: Don T Mock Yourself Out Presentation

rails integration tests๏ model classes (repositories)

๏ model objects

๏ database

๏ views

๏ controllers

๏ routing/sessions

Page 83: Don T Mock Yourself Out Presentation

rails integration tests๏ model classes (repositories)

๏ model objects

๏ database

๏ views

๏ controllers

๏ routing/sessions

!DRY

Page 84: Don T Mock Yourself Out Presentation

the BDD approach

Page 85: Don T Mock Yourself Out Presentation

inherited from XP

Page 86: Don T Mock Yourself Out Presentation

customer specs

developer specs

Page 87: Don T Mock Yourself Out Presentation
Page 88: Don T Mock Yourself Out Presentation

rails integration tests + webrat

shoulda, context, micronaut, etc

Page 89: Don T Mock Yourself Out Presentation

customer specs are implemented as end to end tests

Page 90: Don T Mock Yourself Out Presentation

developer specs are implemented as

isolation tests

Page 91: Don T Mock Yourself Out Presentation

mocking and stubbingwith rails

Page 92: Don T Mock Yourself Out Presentation

partials in view specsdescribe "/registrations/new.html.erb" do before(:each) do template.stub(:render).with(:partial => anything) end it "renders the registration navigation" do template.should_receive(:render).with(:partial => 'nav') render end it "renders the registration form " do template.should_receive(:render).with(:partial => 'form') render endend

Page 93: Don T Mock Yourself Out Presentation

conditional branches incontroller specs

describe "POST create" do describe "with valid attributes" do it "redirects to list of registrations" do registration = stub_model(Registration) Registration.stub(:new).and_return(registration) registration.stub(:save!).and_return(true) post 'create' response.should redirect_to(registrations_path) end endend

Page 94: Don T Mock Yourself Out Presentation

describe "POST create" do describe "with invalid attributes" do it "re-renders the new form" do registration = stub_model(Registration) Registration.stub(:new).and_return(registration) registration.stub(:save!).and_raise( ActiveRecord::RecordInvalid.new(registration)) post 'create' response.should render_template('new') end endend

conditional branches incontroller specs

Page 95: Don T Mock Yourself Out Presentation

describe "POST create" do describe "with invalid attributes" do it "assigns the registration" do registration = stub_model(Registration) Registration.stub(:new).and_return(registration) registration.stub(:save!).and_raise( ActiveRecord::RecordInvalid.new(registration)) post 'create' assigns[:registration].should equal(registration) end endend

conditional branches incontroller specs

Page 96: Don T Mock Yourself Out Presentation

shave a few lines but leave a little stubble

http://github.com/dchelimsky/stubble

Page 97: Don T Mock Yourself Out Presentation

describe "POST create" do describe "with valid attributes" do it "redirects to list of registrations" do stubbing(Registration) do post 'create' response.should redirect_to(registrations_path) end end endend

stubble

Page 98: Don T Mock Yourself Out Presentation

describe "POST create" do describe "with invalid attributes" do it "re-renders the new form" do stubbing(Registration, :as => :invalid) do post 'create' response.should render_template('new') end end it "assigns the registration" do stubbing(Registration, :as => :invalid) do |registration| post 'create' assigns[:registration].should equal(registration) end end endend

stubble

Page 99: Don T Mock Yourself Out Presentation

chainsdescribe UsersController do it "GET 'best_friend'" do member = stub_model(User) friends = stub() friend = stub_model(User) User.stub(:find).and_return(member) member.stub(:friends).and_return(friends) friends.stub(:favorite).and_return(friend) get :best_friend, :id => '37' assigns[:friend].should equal(friend) endend

Page 100: Don T Mock Yourself Out Presentation

chains

describe UsersController do it "GET 'best_friend'" do friend = stub_model(User) User.stub_chain(:find, :friends, :favorite). and_return(friend) get :best_friend, :id => '37' assigns[:friend].should equal(friend) endend

Page 101: Don T Mock Yourself Out Presentation

guidlines, pitfalls, andcommon concerns

Page 102: Don T Mock Yourself Out Presentation

focus on roles

http://www.jmock.org/oopsla2004.pdf

Mock Roles, not Objects

Page 103: Don T Mock Yourself Out Presentation

keep things simple

Page 104: Don T Mock Yourself Out Presentation

avoid tight coupling

Page 105: Don T Mock Yourself Out Presentation

complex setup is a red flag for design

issues

Page 106: Don T Mock Yourself Out Presentation

don’t stub/mock the object you’re testing

Page 107: Don T Mock Yourself Out Presentation

impedes refactoring

Page 108: Don T Mock Yourself Out Presentation

:refactoring => <<-DEFINITION

Improving design without changing behaviour

DEFINITION

Page 109: Don T Mock Yourself Out Presentation

what is behaviour?

Page 110: Don T Mock Yourself Out Presentation
Page 111: Don T Mock Yourself Out Presentation

false positivesdescribe RegistrationsController do describe "GET 'pending'" do it "finds the pending registrations" do pending_registration = stub_model(Registration) Registration.should_receive(:pending). and_return([pending_registration]) get 'pending' assigns[:registrations].should == [pending_registration] end endend

class RegistrationsController < ApplicationController def pending @registrations = Registration.pending endend

Page 112: Don T Mock Yourself Out Presentation

false positivesdescribe RegistrationsController do describe "GET 'pending'" do it "finds the pending registrations" do pending_registration = stub_model(Registration) Registration.should_receive(:pending). and_return([pending_registration]) get 'pending' assigns[:registrations].should == [pending_registration] end endend

class RegistrationsController < ApplicationController def pending @registrations = Registration.pending endend

Page 113: Don T Mock Yourself Out Presentation

false positivesdescribe Registration do describe "#pending" do it "finds pending registrations" do Registration.create! Registration.create!(:pending => true) Registration.pending.should have(1).item end endend

class Registration < ActiveRecord::Base named_scope :pending, :conditions => {:pending => true}end

Page 114: Don T Mock Yourself Out Presentation

false positivesdescribe Registration do describe "#pending" do it "finds pending registrations" do Registration.create! Registration.create!(:pending => true) Registration.pending.should have(1).item end endend

class Registration < ActiveRecord::Base named_scope :pending, :conditions => {:pending => true}end

Page 115: Don T Mock Yourself Out Presentation

false positivesdescribe Registration do describe "#pending" do it "finds pending registrations" do Registration.create! Registration.create!(:pending => true) Registration.pending_confirmation.should have(1).item end endend

class Registration < ActiveRecord::Base named_scope :pending_confirmation, :conditions => {:pending => true}end

Page 116: Don T Mock Yourself Out Presentation

false positivesdescribe Registration do describe "#pending" do it "finds pending registrations" do Registration.create! Registration.create!(:pending => true) Registration.pending_confirmation.should have(1).item end endend

class Registration < ActiveRecord::Base named_scope :pending_confirmation, :conditions => {:pending => true}end

Page 117: Don T Mock Yourself Out Presentation

cucumber

Page 118: Don T Mock Yourself Out Presentation
Page 119: Don T Mock Yourself Out Presentation

http://pragprog.com/titles/achbd/the-rspec-book

http://www.jmock.org/oopsla2004.pdf http://www.mockobjects.com/book/

Mock Roles, not Objectsgrowing object-orientedsoftware, guided by tests

http://xunitpatterns.com/

Page 120: Don T Mock Yourself Out Presentation

http://pragprog.com/titles/achbd/the-rspec-book

http://blog.davidchelimsky.net/http://www.articulatedman.com/

http://rspec.info/http://cukes.info/

Page 121: Don T Mock Yourself Out Presentation

ruby frameworks

Page 122: Don T Mock Yourself Out Presentation

rspec-mocks

http://github.com/dchelimsky/rspec

Page 123: Don T Mock Yourself Out Presentation

mocha

http://github.com/floehopper/mocha

Page 124: Don T Mock Yourself Out Presentation

flexmock

http://github.com/jimweirich/flexmock

Page 126: Don T Mock Yourself Out Presentation

not a mock

http://github.com/notahat/not_a_mock