Don T Mock Yourself Out Presentation

1,729
-1

Published on

Published in: Business, Education
0 Comments
7 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,729
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
46
Comments
0
Likes
7
Embeds 0
No embeds

No notes for slide

Don T Mock Yourself Out Presentation

  1. 1. Don’t Mock Yourself Out David Chelimsky Articulated Man, Inc
  2. 2. http://martinfowler.com/articles/mocksArentStubs.html
  3. 3. Classical and Mockist Testing
  4. 4. Classical and Mockist Testing
  5. 5. Classical and Mockist Testing
  6. 6. classicist mockist
  7. 7. merbist railsist
  8. 8. rspecist testunitist
  9. 9. ist bin ein red herring
  10. 10. The big issue here is when to use a mock http://martinfowler.com/articles/mocksArentStubs.html
  11. 11. agenda ๏ overview of stubs and mocks ๏ mocks/stubs applied to rails ๏ guidelines and pitfalls ๏ questions
  12. 12. test double
  13. 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. 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. 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. 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. 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. 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. 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. 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. 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. 22. method level concepts
  23. 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. 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. 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. 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. 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. 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. 29. things aren’t always as they seem
  30. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 43. stubs are often used like mocks
  44. 44. mocks are often used like stubs
  45. 45. we verify stubs by checking state after an action
  46. 46. we tell mocks to verify interactions
  47. 47. sometimes stubs just make the system run
  48. 48. when are method stubs helpful?
  49. 49. isolation from non-determinism
  50. 50. random values
  51. 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. 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. 53. isolation from external dependencies
  54. 54. network access Database Interface Database Subject Network Internets Interface
  55. 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. 56. network access Stub Database Code Subject Example Stub Network
  57. 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. 58. polymorphic collaborators
  59. 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. 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. 61. when are message expectations helpful?
  62. 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. 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. 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. 65. isolation testing
  66. 66. specifying/testing individual objects in isolation
  67. 67. good fit with lots of little objects
  68. 68. all of these concepts apply to the non-rails specific parts of our rails apps
  69. 69. isolation testing the rails-specific parts of our applicationss
  70. 70. V M C
  71. 71. View Controller Model
  72. 72. Browser Router View Controller Model Database
  73. 73. rails testing ๏ unit tests ๏ functional tests ๏ integration tests
  74. 74. rails unit tests ๏ model classes (repositories) ๏ model objects ๏ database
  75. 75. rails functional tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers
  76. 76. rails functional tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers
  77. 77. rails functional tests ๏ model classes (repositories) ๏ model objects !DRY ๏ database ๏ views ๏ controllers
  78. 78. rails integration tests ๏ model classes (repositories) ๏ model objects ๏ database ๏ views ๏ controllers ๏ routing/sessions
  79. 79. rails integration tests ๏ model classes (repositories) ๏ model objects ๏ database !DRY ๏ views ๏ controllers ๏ routing/sessions
  80. 80. the BDD approach
  81. 81. inherited from XP
  82. 82. customer specs developer specs
  83. 83. rails integration tests + webrat shoulda, context, micronaut, etc
  84. 84. customer specs are implemented as end to end tests
  85. 85. developer specs are implemented as isolation tests
  86. 86. mocking and stubbing with rails
  87. 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. 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. 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. 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. 91. shave a few lines but leave a little stubble http://github.com/dchelimsky/stubble
  92. 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. 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. 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. 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. 96. guidlines, pitfalls, and common concerns
  97. 97. focus on roles Mock Roles, not Objects http://www.jmock.org/oopsla2004.pdf
  98. 98. keep things simple
  99. 99. avoid tight coupling
  100. 100. complex setup is a red flag for design issues
  101. 101. don’t stub/mock the object you’re testing
  102. 102. impedes refactoring
  103. 103. :refactoring => <<-DEFINITION Improving design without changing behaviour DEFINITION
  104. 104. what is behaviour?
  105. 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. 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. 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. 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. 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. 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. 111. cucumber
  112. 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. 113. http://blog.davidchelimsky.net/ http://www.articulatedman.com/ http://rspec.info/ http://cukes.info/ http://pragprog.com/titles/achbd/the-rspec-book
  114. 114. ruby frameworks
  115. 115. rspec-mocks http://github.com/dchelimsky/rspec
  116. 116. mocha http://github.com/floehopper/mocha
  117. 117. flexmock http://github.com/jimweirich/flexmock
  118. 118. rr http://github.com/btakita/rr
  119. 119. not a mock http://github.com/notahat/not_a_mock
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×