Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing Should Be Fun

Noel Rappin
November 03, 2012

Testing Should Be Fun

Kent Beck’s first articles about Test-Driven Development. I don’t think I’m alone in being drawn to TDD at first because it was fun. The quick and consistent feedback and the ability to turn complicated problems into a series of smaller problems to be solved made TDD development seem more like a game than work.

Eventually TDD became less fun. Slow tests made running test suites a cruel joke. Test code gets bulky, and is hard to refactor because, of course, tests don’t have their own tests.

Let’s fix that. We’ll show some notorious testing joy-killers, like slow tests, hard to read tests, tests that paper over bad designs rather than driving new ones. And we’ll squash them, and regain testing delight.

Noel Rappin

November 03, 2012
Tweet

More Decks by Noel Rappin

Other Decks in Technology

Transcript

  1. Green. Green. Green. Green. Green. Green. Green. Green. Green. Green.

    Green. Green. Green. Green. Green. Saturday, November 3, 12
  2. Too much testing to abandon, not enough testing to be

    confident Saturday, November 3, 12
  3. describe User do it "handles true input" do # ...

    end it "handles false input" do # ... end end Bad: Lazy Saturday, November 3, 12
  4. describe User do describe "check_permissions" do it "validates permissions" do

    # ... end it "invalidates no permissions" do # ... end end end Better: Structure by Method Saturday, November 3, 12
  5. describe User do describe "when checking for validations" do it

    "allows an admin user access" do # ... end it "denies a regular user access" do # ... end end end Best: Name by Behavior Saturday, November 3, 12
  6. describe User do it "checks for validations" do user =

    User.new(:role => "admin") post = Post.new(:author => user) user.should be_able_to_see_post(post) user.role = "user" user.should be_able_to_see_post(post) post.author = User.new user.should_not be_able_to_see_post(post) end end Bad: Trying to do too much Saturday, November 3, 12
  7. describe User do it "allows an admin to see a

    post" do user = User.new(:role => "admin") post = Post.new(:author => user) user.should be_able_to_see_post(post) end it "allows a user to see their own post" user = User.new(:role => "user") post = Post.new(:author => user) user.should be_able_to_see_post(post) end end Better: one at a time Saturday, November 3, 12
  8. describe User do it "allows an admin to see a

    post" do user = User.new(:role => "admin") post = Post.new(:author => user) user.should be_able_to_see_post(post) end it "allows a user to see their own post" user = User.new(:role => "user") post = Post.new(:author => user) user.should be_able_to_see_post(post) end end Bad: Duplicate Setup Saturday, November 3, 12
  9. describe User do before(:each) do user = User.new(:role => "admin")

    post = Post.new(:author => user) end it "allows an admin to see a post" do user.should be_able_to_see_post(post) end it "allows a user to see their own post" user.should be_able_to_see_post(post) end end Better: Common setup Saturday, November 3, 12
  10. describe User do it "finds users who have high ratings"

    do (1 .. 10).each do |rating| User.create(:rating => rating, :name => "User #{rating}") end User.high_rating.map(&:name).should =~ ["User 9", "User 10"] end end Bad: Too many objects Saturday, November 3, 12
  11. Better: Just enough objects describe User do it "finds users

    who have high ratings" do high = User.create(:rating => 9) low = User.create(:rating => 8) User.high_rating.should =~ [high] end end Saturday, November 3, 12
  12. Bad: Unnecessary DB hit describe User do it "finds users

    who have high ratings" do high = User.create(:rating => 9) low = User.create(:rating => 8) User.high_rating.should =~ [high] end end Saturday, November 3, 12
  13. Better: No DB Hit describe User do it "finds users

    who have high ratings" do high = User.new(:rating => 9) low = User.new(:rating => 8) User.high_rating.should =~ [high] end end Saturday, November 3, 12
  14. describe User do it "finds users who have high ratings"

    do high = User.new(:rating => 9, :password => "test", :password_confirmation => "test", :username => "test", :email => "[email protected]") low = User.new(:rating => 8, :password => "test", :password_confirmation => "test", :username => "test", :email => "[email protected]") User.high_rating.should =~ [high] end end Bad: Lots of noise Saturday, November 3, 12
  15. Better: Important stuff visible describe User do it "finds users

    who have high ratings" do high = Factory.build(:user, :rating => 9) low = Factory.build(:user, :rating => 8) User.high_rating.map(&:name).should =~ [high] end end Saturday, November 3, 12
  16. Scenario: User logs in Given I am a logged in

    user When I hit my home page Then I see my profile When I go to edit my profile Then my profile is updated When I go to see my content Then my content is available Saturday, November 3, 12
  17. Background: Given I am a logged in user Scenario: User

    logs in When I hit my home page Then I see my profile Scenario: User edits profile When I go to edit my profile Then my profile is updated Saturday, November 3, 12
  18. describe "historical type behavior" do before(:each) do user.deals_for(:restaurant, 2, 5)

    deal.types << :restaurant end it "knows the pct of types for restaurant" do user.pct_bought_for(:restaurant).should == 0.4 end it "knows the score for each restaurant type" do user.history_score_for(:restaurant).should == 90 end end Single assertion Saturday, November 3, 12
  19. Single test describe "historical type behavior" do it "calculates deal

    percentages" user.deals_seen_for(:restaurant, 2, 5) deal.types << :restaurant user.pct_bought_for(:restaurant).should == 0.4 user.history_score_for(:restaurant).should == 90 end end Saturday, November 3, 12
  20. describe UserController do describe "get index" do it "searches properly"

    do @fred = User.create(:name => "Fred") @barney = User.create(:name => "Barney") get :index, :search => "Fred" assigns(:user).should == [@fred] end end end Saturday, November 3, 12
  21. describe User do it "searches properly" do @fred = User.create(:name

    => "Fred") @barney = User.create(:name => "Barney") result = User.search("Fred") result.should == [@fred] end end Using ActiveRecord in check Saturday, November 3, 12
  22. describe User do it "searches properly" do @fred = User.create(:name

    => "Fred") @barney = User.create(:name => "Barney") result = User.search("Fred") result.map(&:name).should == ["Fred"] end end Using literal in check Saturday, November 3, 12
  23. var cheeseburger = { cheeses: function() { // Ajax call

    to cheese server }; }; it('spies on the cheese server', function() { spyOn(cheeseburger, 'cheeses'); cheeseburger.cheeses(); expect(cheeseburger.cheeses).toHaveBeenCalled(); }); Saturday, November 3, 12
  24. it "averages ratings" do vendor.add_ratings(3) deal.regular_price = 16.00 deal.sale_price =

    12.00 user.relevance_of(deal).should == 42.5 end Saturday, November 3, 12
  25. it "averages ratings" do vendor.add_ratings(3) deal.regular_price = 16.00 deal.sale_price =

    12.00 user.relevance_of(deal).should == (60 + (12.0 / 16)) / 2 end Saturday, November 3, 12