asplake

Wednesday, January 11, 2006

Revert that Rakefile!

To my slight embarrassment, I just noticed the following comments at the top of the Rails-generated Rakefile:

# Add your own tasks in files placed in lib/tasks ending in .rake
# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake

So ignore my suggestion to edit your Rakefile, and create a lib/tasks/rcov.rake instead.

Combining the tips from the past couple of posts and tidying up a bit, mine looks like this:

require 'rake/clean'

# output goes to subdirectories of this one
RCOV_OUT = "coverage"

# "rake clobber" to remove output directory (along with other generated stuff)
CLOBBER.include(RCOV_OUT)

# don't report coverage on these files
RCOV_EXCLUDE = %w(boot.rb environment.rb).join(',')

# RCOV command, run as though from the commandline.  Amend as required or perhaps move to config/environment.rb?
RCOV = "/ruby/bin/ruby /ruby/bin/rcov --no-color --exclude #{RCOV_EXCLUDE}"

desc "generate a unit test coverage report in coverage/unit; see coverage/unit/index.html afterwards"
task :coverage_units do
  sh "#{RCOV} --output #{RCOV_OUT}/unit test/unit_tests.rb"
end

desc "generate a functional test coverage report in coverage/functional; see coverage/functional/index.html afterwards"
task :coverage_functional do
  sh "#{RCOV} --output #{RCOV_OUT}/functional test/functional_tests.rb"
end

desc "generate a coverage report for unit and functional tests together in coverage/all; see coverage/all/index.html afterwards"
task :coverage_all do
  sh "#{RCOV} --output #{RCOV_OUT}/all test/all_tests.rb"
end

desc "equivalent to coverage_all"
task :coverage => [:coverage_all]

Technorati tags:

Friday, January 06, 2006

100% test coverage with rcov

Further to my previous post, coverage is now at 100%.

Fixing that last 4% was mostly a matter of:

  • finishing the writing of my tests (obviously). The missing ones were for the post actions in my main controller
  • excluding the standard config files boot.rb and environment.rb from the report, the former because of some standard platform-specific stuff (unreachable in my setup) and the second because (bizarrely) it reported that I wasn't achieving full coverage of its comments! Added --exclude boot.rb,environment.rb as the first argument to rcov.

There was also an issue with code of the following form:

  x = y.collect do |i|
    i.to_s
  end.join("/")

For some reason, rcov failed to recognise the execution of the last line. I had good reason to rewrite this, so I haven't investigated this any further.

Technorati tags:

Wednesday, January 04, 2006

Test coverage with rcov and rake (96.2% at first attempt)

Last night I installed the rcoverage gem (recommended in Agile Web Development with Rails - there I go again!), only to discover in its README that its author recommends a switch to rcov.

Neither tool seems to be (shall we say) over-documented, but I did find Ruby On Rails Test Coverage in Alex Pooley's blog. I followed his instructions and soon had a report on one of my unit tests up and running.

As you may guess from Alex's article, rcov doesn't allow you to specify multiple test programs to run, so you need to maintain a script that will run the whole suite for you.

All that's needed is to require each test script, but I'm far too lazy (not to mention error-prone) to maintain lists of tests manually. Instead, here are three scripts I placed in my tests/ directory, to run unit tests, functional tests and both together.

test/unit_tests.rb:

  # Run all unit tests
    Dir[File.dirname(__FILE__) + "/unit/*_test.rb"].each do |f|
    require f
  end

test/functional_tests.rb:

  # Run all functional tests
  Dir[File.dirname(__FILE__) + "/functional/*_test.rb"].each do |f|
    require f
  end

test/all_tests.rb:

  # Combine unit and functional tests in one run
  %w( unit_tests functional_tests ).each do |f|
    require File.dirname(__FILE__) + "/#{f}.rb"
  end

Finally, I added the following to my project's Rakefile:

  # output directory - removed with "rake clobber" (needs a "require 'rake/clean'" above)
  CLOBBER.include("coverage")

  # RCOV command, run as though from the commandline.  Amend as required or perhaps move to config/environment.rb?
  RCOV = "/ruby/bin/ruby /ruby/bin/rcov"

  desc "generate a unit test coverage report in coverage/unit; see coverage/unit/index.html afterwards"
  task :coverage_units do
    sh "#{RCOV} --output coverage/unit test/unit_tests.rb"
  end

  desc "generate a functional test coverage report in coverage/functional; see coverage/functional/index.html afterwards"
  task :coverage_functional do
    sh "#{RCOV} --output coverage/functional test/functional_tests.rb"
  end

  desc "generate a coverage report for unit and functional tests together in coverage/all; see coverage/all/index.html afterwards"
  task :coverage_all do
    sh "#{RCOV} --output coverage/all test/all_tests.rb"
  end

  desc "equivalent to coverage_all"
  task :coverage => [:coverage_all]

The "desc" lines result in helpful output if I run "rake --tasks". I can never remember them all!

Running the overall coverage report is now as easy as "rake coverage", viewed by pointing my browser at coverage/all/index.html.

For the record, without changing any of my tests, I get an overall coverage rate of 96.2%, which came as quite a pleasant surprise.

One final word of warning: expect tests to run something like 2 orders of magnitude slower when running under rcov. Mine took nearly 5 minutes...

Technorati tags:

Monday, January 02, 2006

40 tests, 115 assertions, 0 failures, 0 errors

I have a fairly complete set of unit tests for my model now. I found one bug in the process, and tidied up my API a bit when I found the tests themselves looking clunky. Coverage is good enough now that I can change something and rely on the tests to pick up any knock-on changes that I've missed.

My only caveat is that I have significantly more test code than model code now, and I might have to redress this a bit with some refactoring. I'm thinking along the lines of some helpers for testing STI, acts_as_list, :dependent => :destroy, etc.

I made a start today on functional tests. With unit testing, I was familiar enough with the concepts but new to doing it in Ruby and with Rails (both of which make it really easy). With functional testing, I'm not too sure where to stop - I'm aiming to test most or all paths through the controllers, but how thoroughly should I test the views? Perhaps a re-read of Mike Clark's chapter in the excellent Agile Web Development with Rails is in order.

Technorati tag: