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

Let's Make Testing Fun Again

Noel Rappin
September 06, 2012

Let's Make Testing Fun Again

“Programmers Love Writing Tests” is the subtitle of one of 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

September 06, 2012
Tweet

More Decks by Noel Rappin

Other Decks in Programming

Transcript

  1. describe User do it "handles true input" do # ...

    end it "handles false input" do # ... end end Bad: Lazy Thursday, September 6, 12
  2. describe User do describe "check_permissions" do it "validates permissions" do

    # ... end it "invalidates no permissions" do # ... end end end Better: Structure by Method Thursday, September 6, 12
  3. 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 Thursday, September 6, 12
  4. 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 Thursday, September 6, 12
  5. 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 Thursday, September 6, 12
  6. 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 Thursday, September 6, 12
  7. Better: Just enough objects 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 Thursday, September 6, 12
  8. 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 Thursday, September 6, 12
  9. 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 Thursday, September 6, 12
  10. describe User do before(:each) do @user = Factory.build(:user, :name =>

    "Fred") end #then some stuff Thursday, September 6, 12
  11. it "uses the user" do @user.name.should == # i don't

    remember end end Thursday, September 6, 12
  12. 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 Thursday, September 6, 12
  13. 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 Thursday, September 6, 12
  14. 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 Thursday, September 6, 12
  15. Single test describe "historical type behavior" do it "calulates 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 Thursday, September 6, 12
  16. 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 Thursday, September 6, 12
  17. 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 Thursday, September 6, 12
  18. 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 Thursday, September 6, 12
  19. 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(); }); Thursday, September 6, 12
  20. 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 Thursday, September 6, 12
  21. 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 Thursday, September 6, 12