A test by any other name

June 24, 2005

· · ·

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

Tags: , , ,

Leave a Comment