Test Coverage in Rails

  • 2,997 views
Uploaded on

This was the eleventh speech of a three day Rails training I gave in Tulsa, OK in the spring 2010.

This was the eleventh speech of a three day Rails training I gave in Tulsa, OK in the spring 2010.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
2,997
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
54
Comments
0
Likes
3

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide








































































































Transcript

  • 1. Test Coverage Building better software
  • 2. Rails Developers Test
  • 3. Rails Developers Test Rails has testing support baked in
  • 4. Rails Developers Test Rails has testing support baked in In fact, it supports many different kinds of testing
  • 5. Rails Developers Test Rails has testing support baked in In fact, it supports many different kinds of testing The Rails culture and community are very pro-testing
  • 6. Rails Developers Test Rails has testing support baked in In fact, it supports many different kinds of testing The Rails culture and community are very pro-testing Many practice Test Driven Development (TDD)
  • 7. Rails Developers Test Rails has testing support baked in In fact, it supports many different kinds of testing The Rails culture and community are very pro-testing Many practice Test Driven Development (TDD) You should too!
  • 8. Testing Advantages
  • 9. Testing Advantages Tests can serve as your memory
  • 10. Testing Advantages Tests can serve as your memory They will remember 30,000 lines of code
  • 11. Testing Advantages Tests can serve as your memory They will remember 30,000 lines of code They will remember for 6 months
  • 12. Testing Advantages Tests can serve as your memory They will remember 30,000 lines of code They will remember for 6 months They will remember for other developers
  • 13. Testing Advantages Tests can serve as your memory They will remember 30,000 lines of code They will remember for 6 months They will remember for other developers Upgrades and changes are easier
  • 14. Testing Advantages Tests can serve as your memory They will remember 30,000 lines of code They will remember for 6 months They will remember for other developers Upgrades and changes are easier It can help prevent regressions (especially for bugs)
  • 15. Testing Advantages Tests can serve as your memory They will remember 30,000 lines of code They will remember for 6 months They will remember for other developers Upgrades and changes are easier It can help prevent regressions (especially for bugs) It increases confidence that your code works
  • 16. Testing Doubts
  • 17. Testing Doubts Testing slows you down (writing more code)
  • 18. Testing Doubts Testing slows you down (writing more code) Myth: remember to add troubleshooting and debugging time when not testing
  • 19. Testing Doubts Testing slows you down (writing more code) Myth: remember to add troubleshooting and debugging time when not testing Testing doesn’t produce perfect code
  • 20. Testing Doubts Testing slows you down (writing more code) Myth: remember to add troubleshooting and debugging time when not testing Testing doesn’t produce perfect code Truth: but it is a tool for producing better code
  • 21. Unit Tests Tests for your models
  • 22. The Goal
  • 23. The Goal Ideally you want 100% coverage
  • 24. The Goal Ideally you want 100% coverage That means changing a line of code should break at least one test
  • 25. The Goal Ideally you want 100% coverage That means changing a line of code should break at least one test That’s rarely achieved
  • 26. The Goal Ideally you want 100% coverage That means changing a line of code should break at least one test That’s rarely achieved Be practical
  • 27. The Goal Ideally you want 100% coverage That means changing a line of code should break at least one test That’s rarely achieved Be practical Tests are about managing risks
  • 28. The Goal Ideally you want 100% coverage That means changing a line of code should break at least one test That’s rarely achieved Be practical Tests are about managing risks Is the risk worth it? (think screen scraping code)
  • 29. Avoid Fixtures
  • 30. Avoid Fixtures Rails has a feature called “fixtures” for loading data into the database for testing
  • 31. Avoid Fixtures Rails has a feature called “fixtures” for loading data into the database for testing Rails clears the test database before each test
  • 32. Avoid Fixtures Rails has a feature called “fixtures” for loading data into the database for testing Rails clears the test database before each test This features creates at least as many problems as it solves and is best avoided
  • 33. Avoid Fixtures Rails has a feature called “fixtures” for loading data into the database for testing Rails clears the test database before each test This features creates at least as many problems as it solves and is best avoided I’ll show how to avoid them manually, but there’s a plugin, called Factory Girl, that is even better
  • 34. What not to Test
  • 35. What not to Test You need to test your code, not Rails itself
  • 36. What not to Test You need to test your code, not Rails itself In other words, don’t test:
  • 37. What not to Test You need to test your code, not Rails itself In other words, don’t test: new(), create(), save(), …
  • 38. What not to Test You need to test your code, not Rails itself In other words, don’t test: new(), create(), save(), … find(), first(), all(), …
  • 39. What not to Test You need to test your code, not Rails itself In other words, don’t test: new(), create(), save(), … find(), first(), all(), … …
  • 40. A Simple Model We are going to add tests for this model
  • 41. $ ruby script/generate model user email:string first_name:string last_name:string A Simple Model We are going to add tests for this model
  • 42. $ ruby script/generate model user email:string first_name:string last_name:string class User < ActiveRecord::Base validates_presence_of :email def full_name return nil if first_name.nil? and last_name.nil? "#{first_name} #{last_name}".strip end end A Simple Model We are going to add tests for this model
  • 43. We Already Have Tests!
  • 44. We Already Have Tests! require 'test_helper' Rails creates a test file class UserTest < ActiveSupport::TestCase # Replace this with your real tests. for each model or test "the truth" do assert true controller it builds end end
  • 45. We Already Have Tests! require 'test_helper' Rails creates a test file class UserTest < ActiveSupport::TestCase # Replace this with your real tests. for each model or test "the truth" do assert true controller it builds end end Run tests with: rake Loaded suite /Users/james/Desktop/ road_tested/test/unit/ user_test Started . Finished in 0.076431 seconds. 1 tests, 1 assertions, 0 failures, 0 errors
  • 46. We Already Have Tests! require 'test_helper' Rails creates a test file class UserTest < ActiveSupport::TestCase # Replace this with your real tests. for each model or test "the truth" do assert true controller it builds end end Run tests with: rake Loaded suite /Users/james/Desktop/ These don’t really test road_tested/test/unit/ user_test anything, but at least Started . Finished in 0.076431 seconds. they are all wired up 1 tests, 1 assertions, 0 failures, 0 errors
  • 47. Test the Validation We are not testing that Rails validations work, we are testing that User requires an email address
  • 48. require 'test_helper' class UserTest < ActiveSupport::TestCase test "email is required" do user = User.new # no email assert(!user.valid?, "User was valid without an email") assert(user.errors.invalid?(:email), "Email was missing but valid") end end Test the Validation We are not testing that Rails validations work, we are testing that User requires an email address
  • 49. require 'test_helper' class UserTest < ActiveSupport::TestCase test "email is required" do user = User.new # no email assert(!user.valid?, "User was valid without an email") assert(user.errors.invalid?(:email), "Email was missing but valid") end end Test the Validation We are not testing that Rails validations work, we are testing that User requires an email address
  • 50. require 'test_helper' class UserTest < ActiveSupport::TestCase test "email is required" do user = User.new # no email assert(!user.valid?, "User was valid without an email") assert(user.errors.invalid?(:email), "Email was missing but valid") end end Test the Validation We are not testing that Rails validations work, we are testing that User requires an email address
  • 51. require 'test_helper' class UserTest < ActiveSupport::TestCase test "email is required" do user = User.new # no email assert(!user.valid?, "User was valid without an email") assert(user.errors.invalid?(:email), "Email was missing but valid") end end Test the Validation We are not testing that Rails validations work, we are testing that User requires an email address
  • 52. require 'test_helper' class UserTest < ActiveSupport::TestCase test "email is required" do user = User.new # no email assert(!user.valid?, "User was valid without an email") assert(user.errors.invalid?(:email), "Email was missing but valid") end end Test the Validation We are not testing that Rails validations work, we are testing that User requires an email address
  • 53. One Aspect of full_name() It’s important to test just one thing at a time
  • 54. require 'test_helper' class UserTest < ActiveSupport::TestCase # ... test "full name is nil if first and last name are nil" do user = User.new # no first_name or last_name assert_nil(user.full_name) end end One Aspect of full_name() It’s important to test just one thing at a time
  • 55. require 'test_helper' class UserTest < ActiveSupport::TestCase # ... test "full name is nil if first and last name are nil" do user = User.new # no first_name or last_name assert_nil(user.full_name) end end One Aspect of full_name() It’s important to test just one thing at a time
  • 56. full_name() Edge Cases Always try to test every edge case you can think up
  • 57. require 'test_helper' class UserTest < ActiveSupport::TestCase # ... test "full name will use just the first name if needed" do user = User.new(:first_name => "James") # no last_name assert_equal(user.first_name, user.full_name) end test "full name will use just the last name if needed" do user = User.new(:last_name => "Gray") # no first_name assert_equal(user.last_name, user.full_name) end end full_name() Edge Cases Always try to test every edge case you can think up
  • 58. require 'test_helper' class UserTest < ActiveSupport::TestCase # ... test "full name will use just the first name if needed" do user = User.new(:first_name => "James") # no last_name assert_equal(user.first_name, user.full_name) end test "full name will use just the last name if needed" do user = User.new(:last_name => "Gray") # no first_name assert_equal(user.last_name, user.full_name) end end full_name() Edge Cases Always try to test every edge case you can think up
  • 59. full_name() Normal Usage Also test the normal intended usage
  • 60. require 'test_helper' class UserTest < ActiveSupport::TestCase # ... test "full name will combine first and last name if possible" do user = User.new(:first_name => "James", :last_name => "Gray") assert_equal("#{user.first_name} #{user.last_name}", user.full_name) end end full_name() Normal Usage Also test the normal intended usage
  • 61. Passing Tests I ran these tests in my code editor (⌘R in TextMate)
  • 62. Passing Tests I ran these tests in my code editor (⌘R in TextMate)
  • 63. Functional Tests Tests for your controllers
  • 64. What not to Test
  • 65. What not to Test It’s OK to decide not to test some things (controversial)
  • 66. What not to Test It’s OK to decide not to test some things (controversial) I don’t test views (controversial)
  • 67. What not to Test It’s OK to decide not to test some things (controversial) I don’t test views (controversial) They change too frequently
  • 68. What not to Test It’s OK to decide not to test some things (controversial) I don’t test views (controversial) They change too frequently They may be changed by designers
  • 69. What not to Test It’s OK to decide not to test some things (controversial) I don’t test views (controversial) They change too frequently They may be changed by designers They shouldn’t contain complex logic anyway
  • 70. What not to Test It’s OK to decide not to test some things (controversial) I don’t test views (controversial) They change too frequently They may be changed by designers They shouldn’t contain complex logic anyway You should still verify that your controllers work
  • 71. A Simple Controller We will add tests for the create action of this controller
  • 72. class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save flash[:notice] = "User created." redirect_to user_path(@user) else flash[:error] = "User could not be created." render :action => :new end end def show @user = User.find(1) end end A Simple Controller We will add tests for the create action of this controller
  • 73. Test Failure and Success I have added a couple of tests that validate the success and failure scenarios of create
  • 74. require 'test_helper' class UsersControllerTest < ActionController::TestCase test "entering a bad user shows the form with errors" do post :create # missing email assert_template(:new) # showed the new form assert_not_nil(flash[:error]) # with errors end test "you are taken to the show page when a user is created" do post :create, :user => {:email => "james@graysoftinc.com"} user = User.find_by_email("james@graysoftinc.com") assert_not_nil(user) # a User was created assert_redirected_to(user_path(user)) # sent to show page end end Test Failure and Success I have added a couple of tests that validate the success and failure scenarios of create
  • 75. require 'test_helper' class UsersControllerTest < ActionController::TestCase test "entering a bad user shows the form with errors" do post :create # missing email assert_template(:new) # showed the new form assert_not_nil(flash[:error]) # with errors end test "you are taken to the show page when a user is created" do post :create, :user => {:email => "james@graysoftinc.com"} user = User.find_by_email("james@graysoftinc.com") assert_not_nil(user) # a User was created assert_redirected_to(user_path(user)) # sent to show page end end Test Failure and Success I have added a couple of tests that validate the success and failure scenarios of create
  • 76. require 'test_helper' class UsersControllerTest < ActionController::TestCase test "entering a bad user shows the form with errors" do post :create # missing email assert_template(:new) # showed the new form assert_not_nil(flash[:error]) # with errors end test "you are taken to the show page when a user is created" do post :create, :user => {:email => "james@graysoftinc.com"} user = User.find_by_email("james@graysoftinc.com") assert_not_nil(user) # a User was created assert_redirected_to(user_path(user)) # sent to show page end end Test Failure and Success I have added a couple of tests that validate the success and failure scenarios of create
  • 77. require 'test_helper' class UsersControllerTest < ActionController::TestCase test "entering a bad user shows the form with errors" do post :create # missing email assert_template(:new) # showed the new form assert_not_nil(flash[:error]) # with errors end test "you are taken to the show page when a user is created" do post :create, :user => {:email => "james@graysoftinc.com"} user = User.find_by_email("james@graysoftinc.com") assert_not_nil(user) # a User was created assert_redirected_to(user_path(user)) # sent to show page end end Test Failure and Success I have added a couple of tests that validate the success and failure scenarios of create
  • 78. require 'test_helper' class UsersControllerTest < ActionController::TestCase test "entering a bad user shows the form with errors" do post :create # missing email assert_template(:new) # showed the new form assert_not_nil(flash[:error]) # with errors end test "you are taken to the show page when a user is created" do post :create, :user => {:email => "james@graysoftinc.com"} user = User.find_by_email("james@graysoftinc.com") assert_not_nil(user) # a User was created assert_redirected_to(user_path(user)) # sent to show page end end Test Failure and Success I have added a couple of tests that validate the success and failure scenarios of create
  • 79. More Passing Tests We have now started some coverage for our controllers as well
  • 80. More Passing Tests We have now started some coverage for our controllers as well
  • 81. Even More Tests Rails doesn’t stop there
  • 82. Other Tests Supported
  • 83. Other Tests Supported Integration tests are used to write system wide tests that cross controllers/actions (like a login system)
  • 84. Other Tests Supported Integration tests are used to write system wide tests that cross controllers/actions (like a login system) You can also test helpers
  • 85. Other Tests Supported Integration tests are used to write system wide tests that cross controllers/actions (like a login system) You can also test helpers I usually don’t bother with simple view logic, but complex systems should be tested
  • 86. Other Tests Supported Integration tests are used to write system wide tests that cross controllers/actions (like a login system) You can also test helpers I usually don’t bother with simple view logic, but complex systems should be tested Rails supports basic performance profiling
  • 87. Other Tests Supported Integration tests are used to write system wide tests that cross controllers/actions (like a login system) You can also test helpers I usually don’t bother with simple view logic, but complex systems should be tested Rails supports basic performance profiling This can be handy when you are tuning
  • 88. Test Driven Development Development practices focused on testing
  • 89. The System
  • 90. The System Add a test
  • 91. The System Add a test Run all tests and see if there is a failure
  • 92. The System Add a test Run all tests and see if there is a failure Write code to make the failing test pass
  • 93. The System Add a test Run all tests and see if there is a failure Write code to make the failing test pass Run all tests to see them succeed
  • 94. The System Add a test Run all tests and see if there is a failure Write code to make the failing test pass Run all tests to see them succeed Refactor code as needed
  • 95. The System Add a test Run all tests and see if there is a failure Write code to make the failing test pass Run all tests to see them succeed Refactor code as needed Repeat
  • 96. Red / Green / Refactor
  • 97. TDD Advantages
  • 98. TDD Advantages The process creates a very tight feedback loop
  • 99. TDD Advantages The process creates a very tight feedback loop This helps find problems much faster
  • 100. TDD Advantages The process creates a very tight feedback loop This helps find problems much faster Dramatically reduces debugging time
  • 101. TDD Advantages The process creates a very tight feedback loop This helps find problems much faster Dramatically reduces debugging time It makes you more aware of YAGNI
  • 102. TDD Advantages The process creates a very tight feedback loop This helps find problems much faster Dramatically reduces debugging time It makes you more aware of YAGNI Drives the design of the code
  • 103. TDD Advantages The process creates a very tight feedback loop This helps find problems much faster Dramatically reduces debugging time It makes you more aware of YAGNI Drives the design of the code Encourages smaller and more modular code
  • 104. Let’s Test Drive a Feature
  • 105. Let’s Test Drive a Feature We currently check that an email is provided
  • 106. Let’s Test Drive a Feature We currently check that an email is provided Let’s add a simple reality check to make sure it looks like an email address
  • 107. Let’s Test Drive a Feature We currently check that an email is provided Let’s add a simple reality check to make sure it looks like an email address This isn’t perfect, but it could catch some mistakes
  • 108. 1. Add a Test I have added the test I expect to fail and my goal will now be to get it to work
  • 109. require 'test_helper' class UserTest < ActiveSupport::TestCase # ... test "email should be well formed" do user = User.new(:email => "not well formed!") assert(!user.valid?, "User was valid with a bad email") assert(user.errors.invalid?(:email), "Email was invalid but allowed") end end 1. Add a Test I have added the test I expect to fail and my goal will now be to get it to work
  • 110. 2. Run all Tests (“Red”) This shows that our new feature isn’t working yet, as we expected
  • 111. 2. Run all Tests (“Red”) This shows that our new feature isn’t working yet, as we expected
  • 112. 3. Write Code I am now adding the code to make the feature work
  • 113. class User < ActiveRecord::Base validates_presence_of :email validates_format_of :email, :with => /A[^@s]+@[^@s]+.[^@s]+z/, :message => "was not well formed" def full_name return nil if first_name.nil? and last_name.nil? "#{first_name} #{last_name}".strip end end 3. Write Code I am now adding the code to make the feature work
  • 114. class User < ActiveRecord::Base validates_presence_of :email validates_format_of :email, :with => /A[^@s]+@[^@s]+.[^@s]+z/, :message => "was not well formed" def full_name return nil if first_name.nil? and last_name.nil? "#{first_name} #{last_name}".strip end end 3. Write Code I am now adding the code to make the feature work
  • 115. 4. Run all Tests (“Green”) This shows that I have succeeded, since my test now passes
  • 116. 4. Run all Tests (“Green”) This shows that I have succeeded, since my test now passes
  • 117. 5. Refactor
  • 118. 5. Refactor Not needed in this case
  • 119. 5. Refactor Not needed in this case This step allows you to clean up messes you create to make a test pass
  • 120. 5. Refactor Not needed in this case This step allows you to clean up messes you create to make a test pass You can refactor and verify that the tests stay Green (you didn’t change things)
  • 121. Questions?
  • 122. Test Your Application Lab Your book has instructions for how to start increasing the test coverage for your application