Ary Borenszweig

Ary Borenszweig

Quick way to test many values in ruby

4 min
Apr 15 2010
testing
4 min
Apr 15 2010

Many times we end up having similar tests: same logic but different inputs and expected outputs. For example, suppose we need to test our brand new sqrt function:

test "sqrt 1" do
  assert_equals 1, sqrt(1)
end

test "sqrt 4" do
  assert_equals 2, sqrt(4)
end

test "sqrt 9" do
  assert_equals 3, sqrt(9)
end

Here we are dupplicating code, since "assert_equals X, sqrt(Y)" is written all over the place. The naive way to refactor this is:

def assert_sqrt(input, output)
  assert_equals output, sqrt(input)
end

test "sqrt 1" do
  assert_sqrt 1, 1
end

test "sqrt 4" do
  assert_sqrt 4, 2
end

test "sqrt 9" do
  assert_sqrt 9, 3
end

Much better.

Some languages provide tools to deal with this problem in a nicer way. In C# you can do this:

[Test]
public void TestSqrt(
  Values(
    new int[] { 1, 1 },
    new int[] { 4, 2 },
    new int[] { 9, 3 }
    ) int[] values) {
  Assert.Equals(values[1], Sqrt(values[0]));
}

So here we are supplying a set of values for our test. (maybe not much nicer, but the code duplication is gone)

The thing I like most about ruby is that the language gives you the tools to get the most out of your code so you don't end up inventing classes, attributes or whatever just to make something as simple as supplying many values to a test.

Here's a better way to do it in ruby:

[[1, 1], [4, 2], [9, 3]].each do |values|
  test "sqrt #{values[0]}"
    assert_equal values[1], sqrt(values[0])
  end
end

Very good!

What makes this possible?

  1. Ruby lets you define code that will be executed at the class definition level
  2. Ruby lets you define methods dynamically (here the test function defines a "test_..." method)

Those two particularities makes the language really nice to work with.

Oh, but wait, there's one more thing. I just found out I can do |input, output| in that last piece of code. It seems that if you iterate an array of arrays, you can specify many arguments to the given block and the i-th argument will match the i-th element of the array, so you don't end up doing values[0] and values[1], which obfuscates the meaning of our code. Thanks again, ruby!

So, here's the final code:

[[1, 1], [4, 2], [9, 3]].each do |input, output|
  test "sqrt #{input}"
    assert_equal output, sqrt(input)
  end
end