Tuesday, September 26, 2006

Test::Unit test creation

Creating a unit test in Ruby is very similar to Java: create a method that begins with test_. In a recent discussion with some colleagues, Zak Tamsen and Muness Alrubaie, Zak described his distaste for this common convention. He continued by pointing out that using .NET Attributes was a superior approach for identifying tests.

This began a discussion on what a superior approach to writing tests could be when using Ruby. We implemented several examples before deciding on our preference.
test
def add_returns_the_sum_of_two_numbers
assert_equal 4, add(2, 2)
end

test 'add returns the sum of two numbers'
assert_equal 4 do
add(2,2)
end
Each implementation contained limitations. In the end we chose to go with a very simple implementation that is similar to how RSpec defines specifications.
test 'add returns the sum of two numbers' do
assert_equal 4, add(2, 2)
end
To accommodate this syntax we added the test class method to Test::Unit::TestCase.
class << Test::Unit::TestCase
def test(name, &block)
test_name = :"test_#{name.gsub(' ','_')}"
raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include? test_name.to_s
define_method test_name, &block
end
end
An additional benefit we were able to include in our change was the ability to raise an error when a test was defined with a duplicate name (or description in our case).

However, moving to this new style of test definition can cause issues. The first noticeable issue was that TextMate would no longer run individual tests. We quickly fixed this issue by looking at the code in the Ruby Bundle. The fix was to change the regex that selects the method you are testing. The code below shows how you can capture either the previous call to the test class method or the previously defined test method.
File.open(ENV['TM_FILEPATH']) do |f|
f.read.split("\n")[0...n].reverse.each do |line|
if line =~ /^\s*test\s+"([_a-z][_a-z0-9 ]*[\?!]?)"\s+do\s*$/i
print "test_#{$1.gsub(' ', '_')}"
break
elsif line =~ /^\s*def ([_a-z][_a-z0-9]*[\?!]?)/i
print $1
break
end
end
end
We also created a TextMate snippet in the Ruby Bundle to help us create tests in our new style. The following code is the snippet for easily creating tests.
test "${1:test name}" do
$0
end
After these quick changes we are quite happy working with our new format for creating tests.
Post a Comment