don t mock yourself out presentation

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

Author: railsconf

Post on 16-May-2015

1.742 views

Category:

Business


3 download

Embed Size (px)

TRANSCRIPT

  • 1. Dont Mock Yourself OutDavid ChelimskyArticulated 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 amock 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 levelconcepts 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 expectationdescribe Statement doit quot;logs a message when printedquot; docustomer = Object.newlogger = Object.newcustomer.stub(:name).and_return('Joe Customer')statement = Statement.new(customer, logger)logger.should_receive(:log).with(/Joe Customer/) statement.printendend 29. things arent 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 endclass 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 endclass 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 endclass 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 endclass 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 endmessage 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 endbound 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 endclass 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 endclass 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 endclass 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 endclass 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 endmessage 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 toimplementation 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 afteran action 46. we tell mocks to verify interactions 47. sometimes stubs just make thesystem 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.newboard.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 accessDatabase Interface DatabaseSubjectNetwork 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 accessStub DatabaseCode Subject ExampleStub 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/ ) endit 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 oflittle objects 68. all of these concepts apply tothe non-railsspecific parts of our rails apps 69. isolation testing therails-specific parts of our applicationss 70. V MC 71. View ControllerModel 72. Browser RouterView 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 asisolation tests 86. mocking andstubbingwith rails 87. partials in view specs describe quot;/registrations/new.html.erbquot; do before(:each) do template.stub(:render).with(:partial => anything) endit quot;renders the registration navigationquot; do template.should_receive(:render).with(:partial => 'nav') render endit quot;renders the registration form quot; do template.should_receive(:render).with(:partial => 'form') render end end 88. conditional branches incontroller specsdescribe quot;POST createquot; dodescribe quot;with valid attributesquot; doit quot;redirects to list of registrationsquot; doregistration = stub_model(Registration)Registration.stub(:new).and_return(registration)registration.stub(:save!).and_return(true)post 'create'response.should redirect_to(registrations_path)endendend 89. conditional branches incontroller specsdescribe quot;POST createquot; dodescribe quot;with invalid attributesquot; doit quot;re-renders the new formquot; doregistration = 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')endendend 90. conditional branches incontroller specsdescribe quot;POST createquot; dodescribe quot;with invalid attributesquot; doit quot;assigns the registrationquot; doregistration = 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)endendend 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 endit 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, andcommon 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 designissues 101. dont stub/mock the object youre testing 102. impedes refactoring 103. :refactoring => {: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 endclass 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 endclass 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 endclass 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-mockshttp://github.com/dchelimsky/rspec 116. mochahttp://github.com/oehopper/mocha 117. flexmockhttp://github.com/jimweirich/exmock 118. rrhttp://github.com/btakita/rr 119. not a mockhttp://github.com/notahat/not_a_mock