Don T Mock Yourself Out Presentation

  • 1,615 views
Uploaded on

 

More in: Business , Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
1,615
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
45
Comments
0
Likes
7

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Don’t Mock Yourself Out David Chelimsky Articulated Man, Inc
  • 2. http://martinfowler.com/articles/mocksArentStubs.html
  • 3. Classical and Mockist Testing
  • 4. Classical and Mockist Testing
  • 5. Classical and Mockist Testing
  • 6. classicist mockist
  • 7. merbist railsist
  • 8. rspecist testunitist
  • 9. ist bin ein red herring
  • 10. The big issue here is when to use a mock http://martinfowler.com/articles/mocksArentStubs.html
  • 11. agenda ๏ overview of stubs and mocks ๏ mocks/stubs applied to rails ๏ guidelines and pitfalls ๏ questions
  • 12. test double
  • 13. test stub describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end
  • 14. test stub describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end
  • 15. test stub describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end
  • 16. test stub describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end
  • 17. mock object describe Statement do it quot;logs a message when printedquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') logger = mock(quot;loggerquot;) statement = Statement.new(customer, logger) logger.should_receive(:log).with(/Joe Customer/) statement.print end end
  • 18. mock object describe Statement do it quot;logs a message when printedquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') logger = mock(quot;loggerquot;) statement = Statement.new(customer, logger) logger.should_receive(:log).with(/Joe Customer/) statement.print end end
  • 19. mock object describe Statement do it quot;logs a message when printedquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') logger = mock(quot;loggerquot;) statement = Statement.new(customer, logger) logger.should_receive(:log).with(/Joe Customer/) statement.print end end
  • 20. mock object describe Statement do it quot;logs a message when printedquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') logger = mock(quot;loggerquot;) statement = Statement.new(customer, logger) logger.should_receive(:log).with(/Joe Customer/) statement.print end end
  • 21. mock object describe Statement do it quot;logs a message when printedquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') logger = mock(quot;loggerquot;) statement = Statement.new(customer, logger) logger.should_receive(:log).with(/Joe Customer/) statement.print end end
  • 22. method level concepts
  • 23. describe Statement do it quot;logs a message when printedquot; 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 end end
  • 24. describe Statement do it quot;logs a message when printedquot; 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 end end
  • 25. describe Statement do it quot;logs a message when printedquot; 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 end end
  • 26. method stub describe Statement do it quot;logs a message when printedquot; 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 end end
  • 27. describe Statement do it quot;logs a message when printedquot; 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 end end
  • 28. message expectation describe Statement do it quot;logs a message when printedquot; 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 end end
  • 29. things aren’t always as they seem
  • 30. describe Statement do it quot;uses the customer name in the headerquot; do customer = mock(quot;customerquot;) statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer') statement.header.should == quot;Statement for Joe Customerquot; end end class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 31. describe Statement do it quot;uses the customer name in the headerquot; do customer = mock(quot;customerquot;) statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer') statement.header.should == quot;Statement for Joe Customerquot; end end class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 32. describe Statement do it quot;uses the customer name in the headerquot; do customer = mock(quot;customerquot;) statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer') statement.header.should == quot;Statement for Joe Customerquot; end end class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 33. describe Statement do it quot;uses the customer name in the headerquot; do customer = mock(quot;customerquot;) statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer') statement.header.should == quot;Statement for Joe Customerquot; end end class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 34. describe Statement do it quot;uses the customer name in the headerquot; do customer = mock(quot;customerquot;) statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer') statement.header.should == quot;Statement for Joe Customerquot; end end message expectation class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 35. describe Statement do it quot;uses the customer name in the headerquot; do customer = mock(quot;customerquot;) statement = Statement.new(customer) customer.should_receive(:name).and_return('Joe Customer') statement.header.should == quot;Statement for Joe Customerquot; end end bound to implementation class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 36. describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 37. describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 38. describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 39. describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 40. describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end ???? class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 41. describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end message expectation class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 42. describe Statement do it quot;uses the customer name in the headerquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') statement = Statement.new(customer) statement.header.should == quot;Statement for Joe Customerquot; end end bound to implementation class Statement def header quot;Statement for #{@customer.name}quot; end end
  • 43. stubs are often used like mocks
  • 44. mocks are often used like stubs
  • 45. we verify stubs by checking state after an action
  • 46. we tell mocks to verify interactions
  • 47. sometimes stubs just make the system run
  • 48. when are method stubs helpful?
  • 49. isolation from non-determinism
  • 50. random values
  • 51. random values class 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) end end
  • 52. time describe Event do it quot;is not happening before the start timequot; do now = Time.now start = now + 1 Time.stub(:now).and_return now event = Event.new(:start => start) event.should_not be_happening end end
  • 53. isolation from external dependencies
  • 54. network access Database Interface Database Subject Network Internets Interface
  • 55. network access def 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.finalize end
  • 56. network access Stub Database Code Subject Example Stub Network
  • 57. network access def test_successful_purchase_sends_shipping_message gateway = stub() gateway.stubs(:authorize).returns( ActiveMerchant::Billing::Response.new(true, quot;ignorequot;) ) item = stub() messenger = mock() messenger.expects(:ship).with(item) purchase = Purchase.new(gateway, item, credit_card, messenger) purchase.finalize end
  • 58. polymorphic collaborators
  • 59. strategies describe Employee do it quot;delegates pay() to payment strategyquot; do payment_strategy = mock() employee = Employee.new(payment_strategy) payment_strategy.expects(:pay) employee.pay end end
  • 60. mixins/plugins describe AgeIdentifiable do describe quot;#can_vote?quot; do it quot;raises if including does not respond to birthdatequot; do object = Object.new object.extend AgeIdentifiable expect { object.can_vote? }.to raise_error( /must supply a birthdate/ ) end it quot;returns true if birthdate == 18 years agoquot; do object = Object.new stub(object).birthdate {18.years.ago.to_date} object.extend AgeIdentifiable object.can_vote?.should be(true) end end end
  • 61. when are message expectations helpful?
  • 62. side effects describe Statement do it quot;logs a message when printedquot; do customer = stub(quot;customerquot;) customer.stub(:name).and_return('Joe Customer') logger = mock(quot;loggerquot;) statement = Statement.new(customer, logger) logger.should_receive(:log).with(/Joe Customer/) statement.print end end
  • 63. caching describe ZipCode do it quot;should only validate oncequot; do validator = mock() zipcode = ZipCode.new(quot;02134quot;, validator) validator.should_receive(:valid?).with(quot;02134quot;).once. and_return(true) zipcode.valid? zipcode.valid? end end
  • 64. interface discovery describe quot;thing I'm working onquot; do it quot;does something with some assistancequot; 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 end end
  • 65. isolation testing
  • 66. specifying/testing individual objects in isolation
  • 67. good fit with lots of little objects
  • 68. all of these concepts apply to the non-rails specific parts of our rails apps
  • 69. isolation testing the rails-specific parts of our applicationss
  • 70. V M C
  • 71. View Controller Model
  • 72. Browser Router View Controller Model Database
  • 73. rails testing ๏ unit tests ๏ functional tests ๏ integration tests
  • 74. rails unit tests ๏ model classes (repositories) ๏ model objects ๏ database
  • 75. rails functional tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers
  • 76. rails functional tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers
  • 77. rails functional tests ๏ model classes (repositories) ๏ model objects !DRY ๏ database ๏ views ๏ controllers
  • 78. rails integration tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers ๏ routing/sessions
  • 79. rails integration tests ๏ model classes (repositories) ๏ model objects ๏ database !DRY ๏ views ๏ controllers ๏ routing/sessions
  • 80. the BDD approach
  • 81. inherited from XP
  • 82. customer specs developer specs
  • 83. rails integration tests + webrat shoulda, context, micronaut, etc
  • 84. customer specs are implemented as end to end tests
  • 85. developer specs are implemented as isolation tests
  • 86. mocking and stubbing with rails
  • 87. partials in view specs describe quot;/registrations/new.html.erbquot; do before(:each) do template.stub(:render).with(:partial => anything) end it quot;renders the registration navigationquot; do template.should_receive(:render).with(:partial => 'nav') render end it quot;renders the registration form quot; do template.should_receive(:render).with(:partial => 'form') render end end
  • 88. conditional branches in controller specs describe quot;POST createquot; do describe quot;with valid attributesquot; do it quot;redirects to list of registrationsquot; 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 end end
  • 89. conditional branches in controller specs describe quot;POST createquot; do describe quot;with invalid attributesquot; do it quot;re-renders the new formquot; 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 end end
  • 90. conditional branches in controller specs describe quot;POST createquot; do describe quot;with invalid attributesquot; do it quot;assigns the registrationquot; 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 end end
  • 91. shave a few lines but leave a little stubble http://github.com/dchelimsky/stubble
  • 92. stubble describe quot;POST createquot; do describe quot;with valid attributesquot; do it quot;redirects to list of registrationsquot; do stubbing(Registration) do post 'create' response.should redirect_to(registrations_path) end end end end
  • 93. stubble describe quot;POST createquot; do describe quot;with invalid attributesquot; do it quot;re-renders the new formquot; do stubbing(Registration, :as => :invalid) do post 'create' response.should render_template('new') end end it quot;assigns the registrationquot; do stubbing(Registration, :as => :invalid) do |registration| post 'create' assigns[:registration].should equal(registration) end end end end
  • 94. chains describe UsersController do it quot;GET 'best_friend'quot; 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) end end
  • 95. chains describe UsersController do it quot;GET 'best_friend'quot; do friend = stub_model(User) User.stub_chain(:find, :friends, :favorite). and_return(friend) get :best_friend, :id => '37' assigns[:friend].should equal(friend) end end
  • 96. guidlines, pitfalls, and common concerns
  • 97. focus on roles Mock Roles, not Objects http://www.jmock.org/oopsla2004.pdf
  • 98. keep things simple
  • 99. avoid tight coupling
  • 100. complex setup is a red flag for design issues
  • 101. don’t stub/mock the object you’re testing
  • 102. impedes refactoring
  • 103. :refactoring => <<-DEFINITION Improving design without changing behaviour DEFINITION
  • 104. what is behaviour?
  • 105. false positives describe RegistrationsController do describe quot;GET 'pending'quot; do it quot;finds the pending registrationsquot; do pending_registration = stub_model(Registration) Registration.should_receive(:pending). and_return([pending_registration]) get 'pending' assigns[:registrations].should == [pending_registration] end end end class RegistrationsController < ApplicationController def pending @registrations = Registration.pending end end
  • 106. false positives describe RegistrationsController do describe quot;GET 'pending'quot; do it quot;finds the pending registrationsquot; do pending_registration = stub_model(Registration) Registration.should_receive(:pending). and_return([pending_registration]) get 'pending' assigns[:registrations].should == [pending_registration] end end end class RegistrationsController < ApplicationController def pending @registrations = Registration.pending end end
  • 107. false positives describe Registration do describe quot;#pendingquot; do it quot;finds pending registrationsquot; do Registration.create! Registration.create!(:pending => true) Registration.pending.should have(1).item end end end class Registration < ActiveRecord::Base named_scope :pending, :conditions => {:pending => true} end
  • 108. false positives describe Registration do describe quot;#pendingquot; do it quot;finds pending registrationsquot; do Registration.create! Registration.create!(:pending => true) Registration.pending.should have(1).item end end end class Registration < ActiveRecord::Base named_scope :pending, :conditions => {:pending => true} end
  • 109. false positives describe Registration do describe quot;#pendingquot; do it quot;finds pending registrationsquot; do Registration.create! Registration.create!(:pending => true) Registration.pending_confirmation.should have(1).item end end end class Registration < ActiveRecord::Base named_scope :pending_confirmation, :conditions => {:pending => true} end
  • 110. false positives describe Registration do describe quot;#pendingquot; do it quot;finds pending registrationsquot; do Registration.create! Registration.create!(:pending => true) Registration.pending_confirmation.should have(1).item end end end class Registration < ActiveRecord::Base named_scope :pending_confirmation, :conditions => {:pending => true} end
  • 111. cucumber
  • 112. http://pragprog.com/titles/achbd/the-rspec-book http://xunitpatterns.com/ growing object-oriented Mock Roles, not Objects software, guided by tests http://www.jmock.org/oopsla2004.pdf http://www.mockobjects.com/book/
  • 113. http://blog.davidchelimsky.net/ http://www.articulatedman.com/ http://rspec.info/ http://cukes.info/ http://pragprog.com/titles/achbd/the-rspec-book
  • 114. ruby frameworks
  • 115. rspec-mocks http://github.com/dchelimsky/rspec
  • 116. mocha http://github.com/floehopper/mocha
  • 117. flexmock http://github.com/jimweirich/flexmock
  • 118. rr http://github.com/btakita/rr
  • 119. not a mock http://github.com/notahat/not_a_mock