A Ruby Test Framework in One Line

May 23, 2008

Time for a little Friday fun with Ruby. I love Ruby for things like this. I realize this has little practical usage, but the fact that it's possible says a lot about the language. You can build a test "framework" in one line of code in Ruby. Let's say we are going to write a method that take 0 to n arguments and adds them up. So following TDD, we write the tests first:

tests = {
  "sum" => 0,
  "sum(7)" => 7,
  "sum(2, 3)" => 5,
  "sum(2, 3, 5)" => 10
}

The tests are defined in a hash where the key is the code to be tested in a string and the value is the expected result. Next we write our implementation:

def sum(*args)
  (args || 0).inject(0){|s,e| s+e}
end

And finally, we create and execute our test framework in one line of code:

tests.each{|e,v| puts((r=eval(e))==v ? ". #{e}" : "! #{e} was '#{r}', expected '#{v}'")}

The output looks like this:

. sum
. sum(2, 3, 5)
. sum(7)
. sum(2, 3)

If we change one of our tests to show a failure, you get this:

. sum
. sum(2, 3, 5)
. sum(7)
! sum(2, 3) was '5', expected '8'

Posted in Technology | Tags Ruby

Comments Comments Feed

1. cute! I like the simplicity of the runner.

But your impl looks flawed.... shouldn't it be:

args.inject(0) {|s,e| s+e}

the || 0 part would only help you if you explicitly passed in nil or false. You might as well blow up if they're passing in bad data.

# Posted By Ryan Davis on Friday, May 23 2008 at 7:19 PM

2. @Ryan

Good point, for some reason I was thinking that passing no args would mean that args would be nil, but it is an empty array. I think this is the right implementation:

args.inject(0){|s,e| s+e.to_i}

Which allows sum(nil, "1", 2) to return 3, which of course I was easily able to add a test for. Yay for TDD!

# Posted By Paul Barry on Friday, May 23 2008 at 9:09 PM

3. Nice one-liner :-)

I've been inspired by this and built a more robust one, but it's 20 lines long :P

def MTest(tests)
if tests[0].is_a?(String) then puts tests.shift end
threads = []
results = {:pass => 0, :fail => 0, :err => 0}
tests.each do |t|
e,p,v = *t
threads << Thread.new do
begin
puts(if (r = p.call) == v
results[:pass] += 1
". #{e}"
else
results[:fail] += 1
"! #{e} was #{r}, expected #{v}"
end)
rescue => x
results[:err] += 1
puts "@ #{x.class} at line #{x.backtrace[0].split(':')[1]}"
end
end
end
threads.join
results
end

This allows you many tricks like naming your tests, don't abort on exceptions but show a notice and get a summary for your tests.

# Posted By François Vaux on Friday, May 30 2008 at 5:23 AM

4. I forgot to notice that the test format is now as follows :

tests = [
  "= Some Basic Tests =",
  ['true', proc { true }, true],
  ['true', proc { false }, true],
  ['sum(2,5)', proc { sum(2,5) }, 7]
]

And you just have to call MTest(tests)

(sorry for screwing the indent in my last post)

# Posted By François Vaux on Friday, May 30 2008 at 5:25 AM

5. outstanding!

# Posted By Kerry Gunther on Tuesday, August 5 2008 at 3:55 PM

6. Devver, a TechStars startup, is releasing its developer service to speed up running Ruby testing frameworks.

http://www.pupuweb.com/blog/devver-speed-up-ruby-testing-frameworks/

# Posted By Alex Lim on Tuesday, June 23 2009 at 9:42 AM

Comments Disabled