Example of Parameterized Tests with Ruby.
Parameterized tests – where the data is extracted and fed into a common test block – are an easy way to focus on the edge cases, and to write shorter, clearer code.
As an example, I refactored the tests of a utility in the Logue Ruby gem (an alternative to the Log module of the Ruby Standard Library), reducing the size significantly:
Before:
#!/usr/bin/ruby -w
# -*- ruby -*-
require 'test/unit'
require 'logue/pathutil'
class Logue::PathUtilTestCase < Test::Unit::TestCase
# trim_left
def run_trim_left_test expected, length, str = "something"
trimmed = Logue::PathUtil.trim_left str, length
assert_equal expected, trimmed
end
def test_trim_left_short_positive_number
run_trim_left_test "some", 4
end
def test_trim_left_long
run_trim_left_test "something", 10
end
def test_trim_left_short_negative_number
run_trim_left_test "some", -4
end
# trim_right
def assert_trim_right expected, length, str
trimmed = Logue::PathUtil.trim_right str, length
assert_equal expected, trimmed, "length: #{length}"
end
def test_trim_right_path_excess
assert_trim_right "ab/cd/ef.t", 11, "ab/cd/ef.t"
end
def test_trim_right_path_at_length
assert_trim_right "ab/cd/ef.t", 10, "ab/cd/ef.t"
end
def test_trim_right_path_one_less
assert_trim_right ".../ef.t", 9, "ab/cd/ef.t"
end
def test_trim_right_path_two_less
assert_trim_right ".../ef.t", 8, "ab/cd/ef.t"
end
def test_trim_right_path_three_less
assert_trim_right "ef.t", 7, "ab/cd/ef.t"
end
def test_trim_right_path_four_less
assert_trim_right "ef.t", 6, "ab/cd/ef.t"
end
def test_trim_right_path_five_less
assert_trim_right "ef.t", 5, "ab/cd/ef.t"
end
def test_trim_right_path_six_less
assert_trim_right "ef.t", 4, "ab/cd/ef.t"
end
def test_trim_right_path_seven_less
assert_trim_right "ef.t", 3, "ab/cd/ef.t"
end
def test_trim_right_path_eight_less
assert_trim_right "ef.t", 2, "ab/cd/ef.t"
end
end
After:
#!/usr/bin/ruby -w
# -*- ruby -*-
require 'logue/pathutil'
require 'test/unit'
require 'paramesan'
class Logue::PathUtilTestCase < Test::Unit::TestCase
include Paramesan
param_test [
["abcd", "abcdef", 4 ],
["abc", "abcdef", 3 ],
["abcdef", "abcdef", 10 ],
["abcd", "abcdef", -4 ],
["abc", "abcdef", -3 ],
].each do |exp, str, len|
trimmed = Logue::PathUtil.trim_left str, len
assert_equal exp, trimmed
end
param_test [
[ "ef.t", "ab/cd/ef.t", 2 ],
[ "ab/cd/ef.t", "ab/cd/ef.t", 11 ],
[ "ab/cd/ef.t", "ab/cd/ef.t", 10 ],
[ ".../ef.t", "ab/cd/ef.t", 9 ],
[ ".../ef.t", "ab/cd/ef.t", 8 ],
[ "ef.t", "ab/cd/ef.t", 7 ],
[ "ef.t", "ab/cd/ef.t", 6 ],
[ "ef.t", "ab/cd/ef.t", 5 ],
[ "ef.t", "ab/cd/ef.t", 4 ],
[ "ef.t", "ab/cd/ef.t", 3 ],
[ "ef.t", "ab/cd/ef.t", 2 ],
].each do |exp, str, len|
trimmed = Logue::PathUtil.trim_right str, len
assert_equal exp, trimmed
end
end
The code is much shorter, 39 lines instead of 74. Also notice that the first statement is to require the file of the code under test. By making that the first line, it makes the test fail more quickly if there are dependency issues.
It also means that there is no need to figure out distinct test method names, which Paramesan generates, as would be seen in a failing test:
/opt/org/incava/logue/test/logue/pathutil_test.rb:36:in `block in <class:PathUtilTestCase>'
<".../ef.t"> expected but was
<"ef.t">
diff:
? .../ef.t
Failure: test__ef_t_ab_cd_ef_t_3_(Logue::PathUtilTestCase)