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.
testEach implementation contained limitations. In the end we chose to go with a very simple implementation that is similar to how RSpec defines specifications.
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
test 'add returns the sum of two numbers' doTo accommodate this syntax we added the
assert_equal 4, add(2, 2)
end
test
class method to Test::Unit::TestCase.class << Test::Unit::TestCaseAn 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).
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
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|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.
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
test "${1:test name}" doAfter these quick changes we are quite happy working with our new format for creating tests.
$0
end
Why not just use rspec then?
ReplyDeleteHmmm... interesting. This might have been a nice choice:
ReplyDeletetest 'add returns the sum of two numbers'
def test01
assert_equal 4 do
add(2,2)
end
end
The method name could be anything, but would act as a unique tag, as opposed to the description.
Was there a particular reasoning as to why aspects where better?
ReplyDelete@Pat, Using RSpect would actually be my preference, but it doesn't currently work well with Mocha.
ReplyDelete@tea42, We also considered that option, but didn't like the idea of arbitrary test names. We thought about generating the test names also, but never found a syntax that seemed superior.
@Dan, I don't understand the question, sorry.
Jay, what problems do you get using RSpec with Mocha?
ReplyDelete@Pat, I haven't tried since there are known issues that James Mead is working on. I'm on both the RSpec & Mocha mailing lists and both camps are interested in working together. I expect the issues to be resolved very soon. At which time I expect to write about how wonderful RSpect is. ;)
ReplyDeletere: rspec and mocha. What's happening in the short term is that rspec is enhancing it's own mocking framework to support class level mocking/stubbing. Expect that in a release in the next week or so.
ReplyDeleteAt the same time, we're working out a plugin model that any framework can use to plug itself into rspec. That should be available soon as well, at which point you'll be able to chose between rspec's built in mocking/stubbing and mocha/stubba, flexmock, et al. Even if those frameworks don't ship w/ the plug needed to plug into rspec, it should be easy enough for you to write your own connector at that point (and/or contribute one to the framework of your choosing).
Of course, all of that is theoretical at this point ;)