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. Let's Make Testing Fun Again Noel Rappin Thursday, September 6,

    12
  2. Who is this guy? Thursday, September 6, 12

  3. Kent Beck Thursday, September 6, 12

  4. Thursday, September 6, 12

  5. 1998 Thursday, September 6, 12

  6. In 2001, this was my RSpec Thursday, September 6, 12

  7. Thursday, September 6, 12

  8. Avdi Grimm, "Confident Ruby" Thursday, September 6, 12

  9. "This talk is fundamentally about joy." Thursday, September 6, 12

  10. Do we still love writing tests? Thursday, September 6, 12

  11. Or do we just love having written tests? Thursday, September

    6, 12
  12. Or do we just love saying that we've written tests?

    Thursday, September 6, 12
  13. What makes testing fun? Thursday, September 6, 12

  14. Feedback Thursday, September 6, 12

  15. Communication Thursday, September 6, 12

  16. Given When Then Thursday, September 6, 12

  17. Given When Then Setup Run Thursday, September 6, 12

  18. Setup Thursday, September 6, 12

  19. Name accurately Thursday, September 6, 12

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

    end it "handles false input" do # ... end end Bad: Lazy Thursday, September 6, 12
  21. 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
  22. 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
  23. Take a small, exact step Thursday, September 6, 12

  24. 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
  25. 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
  26. Given Thursday, September 6, 12

  27. Minimize object use Thursday, September 6, 12

  28. 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
  29. 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
  30. Clarity on object creation Thursday, September 6, 12

  31. describe User do it "finds users who have high ratings"

    do high = User.new(:rating => 9, :password => "test", :password_confirmation => "test", :username => "test", :email => "test@test.com") low = User.new(:rating => 8, :password => "test", :password_confirmation => "test", :username => "test", :email => "test@test.com") User.high_rating.should =~ [high] end end Bad: Lots of noise Thursday, September 6, 12
  32. 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
  33. Keep relevant setup close Thursday, September 6, 12

  34. describe User do before(:each) do @user = Factory.build(:user, :name =>

    "Fred") end #then some stuff Thursday, September 6, 12
  35. #and more stuff #and more stuff Thursday, September 6, 12

  36. it "uses the user" do @user.name.should == # i don't

    remember end end Thursday, September 6, 12
  37. When Thursday, September 6, 12

  38. Test one action at a time Thursday, September 6, 12

  39. 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
  40. 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
  41. Invoke the action at the right level Thursday, September 6,

    12
  42. Cucumber makes a lousy unit test framework Thursday, September 6,

    12
  43. Then Thursday, September 6, 12

  44. Single Assertion Thursday, September 6, 12

  45. 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
  46. 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
  47. Test the right thing Thursday, September 6, 12

  48. 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
  49. Test Simple Values Thursday, September 6, 12

  50. 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
  51. 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
  52. Spy, don't mock Thursday, September 6, 12

  53. 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
  54. Magic literal testing Thursday, September 6, 12

  55. 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
  56. 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
  57. Double sided booleans Thursday, September 6, 12

  58. Running Thursday, September 6, 12

  59. Reduce Friction Thursday, September 6, 12

  60. Display clearly Thursday, September 6, 12

  61. After Thursday, September 6, 12

  62. Listen to your tests Thursday, September 6, 12

  63. Refactoring Thursday, September 6, 12

  64. Don't Shortcut the Process Thursday, September 6, 12

  65. Avoid the Uncanny Testing Valley Thursday, September 6, 12

  66. Developer testing is a means to an end Thursday, September

    6, 12
  67. @noelrap http://www.noelrappin.com/mstwjs WCR_25 Thursday, September 6, 12