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)

Related