Diasparsoft Logo Let's write software that people understand.

Home | Contact

Training

JUnit support

Outsourcing

The work that Diasparsoft did for us was outstanding. We are now using the software on a daily basis and their work could well have a dramatic impact on the Reds' organization in the near future.Cincinnati Reds Baseball Club.


Publications

Tips & Tricks

Diasparsoft Toolkit

What is Diaspar?

Interesting Bits RSS

Home » Archives » June 2004 » Parameterized Test Case, Ruby style

[Previous entry: "Eclipse 3.0RC1 and JUnit"] [Next entry: "A comment on "Domain-Centric Programming""]

06/07/2004: "Parameterized Test Case, Ruby style"


Curt Sampson contributed a neat idea to the junit Yahoo! group about implementing the Parameterized Test Case pattern idiomatically in Ruby. The result is startling, compared to the corresponding JUnit code.

Without further ado, I'll let Curt have the floor:

Anyway, I've appended a little example I was playing with this morning where I ended up making a test suite that was just a hash of inputs and expected results, something you'd typically have to build up a parameterized test case for. The key code ends up being, instead, just:

fibtest_hash = { 0 => 0, 1 => 1, 2 => 1, 3 => 2, 4 => 3, 5 => 5, 6 => 8 }
 
def fibtest_hash.calculate_result(input)
    Fib.new.fib(input)
end
 
fibtest_hash.extend(TestEachInputExpectedPair)
 
Test::Unit::UI::Console::TestRunner.run(fibtest_hash)

Of course, that object doesn't have to be a Hash. It might be, for example, a database query that pulls data from test runs against an oracle, or even the oracle itself:

suite = SQLQuery.new("SELECT input, result FROM old_test_run")
 
def suite.calculate_result(input)
    ...
end
 
suite.extend(TestEachInputExpectedPair)
 
Test::Unit::UI::Console::TestRunner.run(suite)

Unfortunately, I'm out of time today to think about this further....


Here is the code that Curt provided for TestEachInputExpectedPair, which provides a custom suite method:

module TestEachInputExpectedPair
 
    def suite
        suite = Test::Unit::TestSuite.new(self.to_s)
        each_pair { |input, expected|
            suite << InputAndExpectedTestCase.new(input, expected) { |i|
                self.calculate_result(i)
            }
        }
        return suite
    end
 
end

Any object that implements the method each_pair can extend this module to mix in the custom suite creation ability. In JUnit, you have to do two things:

With this approach, the custom suite extraction is written Once And Only Once! Now here is the test case class:

class InputAndExpectedTestCase < Test::Unit::TestCase
 
    def initialize(input, expected, &calculate_result)
        super("run_test")
        @input = input
        @expected = expected
        @calculate_result = calculate_result
    end
 
    def name
        return "input #{@input}"
    end
 
    def run_test
        assert_equal(@expected, @calculate_result.call(@input))
    end
 
end

This is quite straightforward, and the result is powerful. Curt has reduced all that extra typing to these basic steps:

  1. Create a collection mapping inputs to expected values.
  2. Implement each_pair on the object.
  3. Implement calculate_result to provide the engine of the test, which in JUnit you would do by overriding runTest().
  4. Have the test data collection extend TestEachInputExpectedPair. The resulting object becomes the custom-built suite for the Parameterized Test Case.

Isn't dynamic typing great?! I need to write more Ruby.