Slide 1

Slide 1 text

Testing Should Be Fun Noel Rappin @noelrap Saturday, November 3, 12

Slide 2

Slide 2 text

Who is this guy? Saturday, November 3, 12

Slide 3

Slide 3 text

Kent Beck Saturday, November 3, 12

Slide 4

Slide 4 text

Saturday, November 3, 12

Slide 5

Slide 5 text

Saturday, November 3, 12

Slide 6

Slide 6 text

In 2001, this was my RSpec Saturday, November 3, 12

Slide 7

Slide 7 text

Saturday, November 3, 12

Slide 8

Slide 8 text

Avdi Grimm, "Confident Ruby" Saturday, November 3, 12

Slide 9

Slide 9 text

"This talk is fundamentally about joy." Saturday, November 3, 12

Slide 10

Slide 10 text

Do we still love writing tests? Saturday, November 3, 12

Slide 11

Slide 11 text

Or do we just love having written tests? Saturday, November 3, 12

Slide 12

Slide 12 text

Or do we just love saying that we've written tests? Saturday, November 3, 12

Slide 13

Slide 13 text

What makes testing fun? Saturday, November 3, 12

Slide 14

Slide 14 text

Feedback Saturday, November 3, 12

Slide 15

Slide 15 text

Communication Saturday, November 3, 12

Slide 16

Slide 16 text

What makes testing painful? Saturday, November 3, 12

Slide 17

Slide 17 text

Long run times Flakey Behavior Impentrable Saturday, November 3, 12

Slide 18

Slide 18 text

Big Test Suites Saturday, November 3, 12

Slide 19

Slide 19 text

Red. Green. Refactor. Saturday, November 3, 12

Slide 20

Slide 20 text

The Real TDD Cycle: Saturday, November 3, 12

Slide 21

Slide 21 text

Red. Green. Refactor.... Saturday, November 3, 12

Slide 22

Slide 22 text

Green. Green. Green. Green. Green. Green. Green. Green. Green. Green. Green. Green. Green. Green. Green. Saturday, November 3, 12

Slide 23

Slide 23 text

Tests are run way, way more than they are written Saturday, November 3, 12

Slide 24

Slide 24 text

How many times does a test suite run per day? Saturday, November 3, 12

Slide 25

Slide 25 text

What does it mean to add an extra test? Saturday, November 3, 12

Slide 26

Slide 26 text

Time Clarity Error Rate Saturday, November 3, 12

Slide 27

Slide 27 text

Tests can be legacy too Saturday, November 3, 12

Slide 28

Slide 28 text

The uncanny testing valley Saturday, November 3, 12

Slide 29

Slide 29 text

Too much testing to abandon, not enough testing to be confident Saturday, November 3, 12

Slide 30

Slide 30 text

Details Matter Saturday, November 3, 12

Slide 31

Slide 31 text

Why is refactoring part of the TDD cycle? Saturday, November 3, 12

Slide 32

Slide 32 text

Refactor Tests Saturday, November 3, 12

Slide 33

Slide 33 text

Some Test Refactorings Saturday, November 3, 12

Slide 34

Slide 34 text

Given When Then Setup Run Saturday, November 3, 12

Slide 35

Slide 35 text

Setup Saturday, November 3, 12

Slide 36

Slide 36 text

Apply Behavioral Name Saturday, November 3, 12

Slide 37

Slide 37 text

describe User do it "handles true input" do # ... end it "handles false input" do # ... end end Bad: Lazy Saturday, November 3, 12

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Extract test Saturday, November 3, 12

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Extract setup Saturday, November 3, 12

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Given Saturday, November 3, 12

Slide 47

Slide 47 text

Remove Extraneous Object Saturday, November 3, 12

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Remove Database Creation Saturday, November 3, 12

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Remove default values Saturday, November 3, 12

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

When Saturday, November 3, 12

Slide 57

Slide 57 text

Separate actions Saturday, November 3, 12

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Invoke the action at the right level Saturday, November 3, 12

Slide 61

Slide 61 text

Cucumber makes a lousy unit test framework Saturday, November 3, 12

Slide 62

Slide 62 text

Then Saturday, November 3, 12

Slide 63

Slide 63 text

Single Assertion Saturday, November 3, 12

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

Test the right thing Saturday, November 3, 12

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Assert Primitive Object Saturday, November 3, 12

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Switch to Spy Saturday, November 3, 12

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

Don't Assert Magic Literal Saturday, November 3, 12

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Complement Boolean Test Saturday, November 3, 12

Slide 77

Slide 77 text

Running Saturday, November 3, 12

Slide 78

Slide 78 text

Reduce Friction Saturday, November 3, 12

Slide 79

Slide 79 text

Display clearly Saturday, November 3, 12

Slide 80

Slide 80 text

After Saturday, November 3, 12

Slide 81

Slide 81 text

Listen to your tests Saturday, November 3, 12

Slide 82

Slide 82 text

Are all these tests still needed? Saturday, November 3, 12

Slide 83

Slide 83 text

Refactor Your Tests Saturday, November 3, 12

Slide 84

Slide 84 text

Don't Shortcut the Process Saturday, November 3, 12

Slide 85

Slide 85 text

Avoid the Uncanny Testing Valley Saturday, November 3, 12

Slide 86

Slide 86 text

What do programmers love? Saturday, November 3, 12

Slide 87

Slide 87 text

Developer testing is a means to an end Saturday, November 3, 12

Slide 88

Slide 88 text

@noelrap http://www.groupon.com/techjobs http://www.noelrappin.com/mstwjs RUBY_2012_25 Saturday, November 3, 12