A test by any other name
RubyOnRails comes with built-in support for unit and functional testing – great for us test-driven development whackos. In a new rails application we’re developing, we used the excellent salted hash login generator to kick off the development of our site’s authentication. The login generator ships with tests – yippee! Since it’s a widely used extension for rails, it makes a useful example for some testing best practices that we’ve adopted.
[[Update: if you follow the conventions outlined here, you should be able to use the agiledox browser that I write about here]]
Here’s an example of one of the tests:
def test_auth_bob
@request.session['return-to'] = "/bogus/location"
post :login, :user => { "login" => "bob", "password" => "atest" }
assert_session_has "user"
assert_equal @bob, @response.session["user"]
assert_redirect_url "/bogus/location"
end
This test checks that the on successful login, the appropriate user is set in the session and that the browser is sent a redirect to a location stored in the session. I like my test methods to test one thing at a time – it becomes easier to narrow down any cause of failure. So, I split the two tests:
def test_auth_bob_1
@request.session['return-to'] = "/bogus/location"
post :login, :user => { "login" => "bob", "password" => "atest" }
assert_redirect_url "/bogus/location"
end
def test_auth_bob_2
post :login, :user => { "login" => "bob", "password" => "atest" }
assert_session_has "user"
assert_equal @bob, @response.session["user"]
end
Now, we have two tests: one that checks that the controller redirects and the other that the session has the user set. But we have some duplication that’s crept in and we need to do a bit of work on the test names.
def log_in_with_valid_user
post :login, :user => { "login" => "bob", "password" => "atest" }
end
def test_should_redirect_to_page_stored_in_session_on_successful_login
@request.session[:return_to] = "/bogus/location"
log_in_with_valid_user
assert_redirect_url "/bogus/location"
end
def test_should_store_user_object_in_session_on_successful_login
log_in_with_valid_user
assert_session_has :user
assert_equal @bob, @response.session[:user]
end
We find that the pattern ‘test_should_***_on_***’ a useful way of naming tests – it’s an idea stolen from JBehave . If this test were to fail, I’m reminded that I should (!) ask myself the question ‘should the class I’m testing do this?’ before I go on a bug-hunt. The other advantage is that I can use my test classes to generate some simple documentation for my classes. Here’s a rake target that can do just that:
desc "Generate agiledox-like documentation for tests"
task :agiledox do
tests = FileList['test/**/*_test.rb']
tests.each do |file|
m = %r".*/([^/].*)_test.rb".match(file)
puts m[1]+" should:\n"
test_definitions = File::readlines(file).select {|line| line =~ /.*def test.*/}
test_definitions.each do |definition|
m = %r"test_(should_)?(.*)".match(definition)
puts " - "+m[2].gsub(/_/," ")
end
puts "\n"
end
end
An example from our codebase, typing rake agiledox generates:
security_controller should: - redirect to page stored in session on successful login - store user object in session on successful login - redirect to page stored in session after signup - store user object in session after signup - reject signup when passwords do not match - reject signup when login too short - report both errors if passwords dont match and username too short - not store user in session if password not correct on signup - remain on login page if password not correct on signup - remove user from session on log out
I'm Ben Griffiths: