The document discusses testing in Rails applications. It notes that Rails has built-in support for testing and that the Rails community emphasizes testing. It recommends practicing test-driven development. It lists advantages of testing like serving as documentation, making changes and upgrades easier, and increasing confidence that code works. It addresses doubts about testing slowing development and not producing perfect code. It provides examples of unit tests for a sample User model, testing validations, methods, and edge cases. It also provides an example of controller tests for a UsersController.
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!
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
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
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)
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
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
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
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
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
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
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
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
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
101. TDD Advantages
The process creates a very tight feedback loop
This helps find problems much faster
Dramatically reduces debugging time
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
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
104. 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
106. Let’s Test Drive a Feature
We currently check that an email is provided
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
108. 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
109. 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. 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
111. 2. Run all Tests (“Red”)
This shows that our new feature isn’t working
yet, as we expected
112. 2. Run all Tests (“Red”)
This shows that our new feature isn’t working
yet, as we expected
113. 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. 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
116. 4. Run all Tests (“Green”)
This shows that I have succeeded,
since my test now passes
117. 4. Run all Tests (“Green”)
This shows that I have succeeded,
since my test now passes
120. 5. Refactor
Not needed in this case
This step allows you to clean up messes you create to
make a test pass
121. 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)