25

I have Ruby 2.0 code that operates on phone numbers, which I want to test using MiniTest. I have a function which takes a phone number arguments and tests it (including asserts). Each time I call that function, I want it to be a new test case. Something like this:

listOfPhoneNumbersForTesting.each { |phone|   testphone phone }  

What I DO NOT want is this:

class test2125551212 < MiniTest::Unit::TestCase
    def t2125551212 
        testphone "2125551212"
    end
end

... repeated 10, 20, or 100 times to test each phone number ...

Obviously, I could put the loop code within a MiniTest::Unit::TestCase, but that results in just one testcase no matter how many phone numbers I test, and I don't like that. (Also, if one of the asserts fail, then no more phone numbers are tested, and I don't want that!) Also the second form looks like a violation of DRY to me, since the class name, function name, and argument all contain the phone number.

Somehow I feel that I should be able to have one class called TestPhone, and create it with the phone number argument, and get that into the MiniTest framework. but I would be willing to use setup(), Fixtures, metaprogramming, or just about anything else, if it would work.

listOfPhoneNumbersForTesting.each { |phone|   TestPhone.new phone }

Where TestPhone is a TestCase subclass, and ends up calling testphone to do the work.

Basically, what I want is this: 1. One list of phone numbers, and if I add a number to the list, I get one more TestCase in reporting output. 2. If the tests associated with one phone number fail, the rest are still tested. 3. All phone numbers get the same testing, which includes several assertions.

Thanks very much!

user2773661
  • 253
  • 3
  • 5

1 Answers1

31

You could dynamically define the methods.

In the following example, 6 tests are dynamically created (2 tests for each of the 3 values being tested). This means that if anything fails, the other tests still run.

require "minitest/autorun"
class MyTests < MiniTest::Unit::TestCase
    ['0', '1111111', '2222222'].each do |phone_number|
        define_method("test_#{phone_number}_has_7_characters") do
            assert_equal(7, phone_number.length)
        end

        define_method("test_#{phone_number}_starts_with_1") do
            assert_equal('1', phone_number[0])
        end
    end
end

The apply test case gives the following results:

# Running tests:

F..F.F

Finished tests in 0.044004s, 136.3512 tests/s, 136.3512 assertions/s.

  1) Failure:
test_0_starts_with_1(MyTests) [stuff.rb:13]:
Expected: "1"
  Actual: "0"

  2) Failure:
test_0_has_7_characters(MyTests) [stuff.rb:9]:
Expected: 7
  Actual: 1

  3) Failure:
test_2222222_starts_with_1(MyTests) [stuff.rb:13]:
Expected: "1"
  Actual: "2"

6 tests, 6 assertions, 3 failures, 0 errors, 0 skips

Applying the same concept to your tests, I think you want:

class MyTests < MiniTest::Unit::TestCase
    listOfPhoneNumbersForTesting.each do |phone|
        define_method("test_#{phone}") do
            TestPhone.new phone
        end
    end
end

A similar approach can be taken when using the spec-style tests:

require 'minitest/spec'
require 'minitest/autorun'

describe "my tests" do
  ['0', '1111111', '2222222'].each do |phone_number|
    it "#{phone_number} has 7 characters" do
      assert_equal(7, phone_number.length)
    end

    it "#{phone_number} starts with 1" do
      assert_equal('1', phone_number[0])
    end
  end
end

IMPORTANT: One thing to note is that you need to make sure that the name of the test methods created are unique for each test case.

For example, if you do not put the phone number in the method name, you end up overwriting your previously defined methods. Ultimately this means that only the last phone number gets tested.

This is because MiniTest generates the test methods on the fly and will overwrite already generated test methods, ultimately only using the last .each variable.

mahatmanich
  • 10,791
  • 5
  • 63
  • 82
Justin Ko
  • 46,526
  • 5
  • 91
  • 101
  • Justin, this answered my question perfectly. Thanks very much! – user2773661 Sep 12 '13 at 22:16
  • 1
    This is an awesome solution for parameterizing test cases. I'm adding parameterized as a comment in hopes that people searching google for ruby minitest parameterized test cases (like me for the last 2 hours) might find this lovely solution. THANKS! – Marty Neal Mar 13 '14 at 23:18
  • is this possible with spec style tests? if so could you provide an example? cheers – mahatmanich Mar 24 '15 at 13:17
  • @mahatmanich, yes it is possible with spec style tests. As seen in the added example, it is a similar approach. – Justin Ko Mar 24 '15 at 19:38
  • I tried this but it would not allow the .each in there? – mahatmanich Mar 24 '15 at 20:37
  • Yeah I might do that, anyway thanks for the syntax, I'll try that tomorrow. Can't test test right now:-) – mahatmanich Mar 24 '15 at 20:49
  • I just stumbled upon a pitfall which you mentioned, I highlighted it in bold. – mahatmanich Mar 25 '15 at 13:16
  • how would you do a ```setup``` or ```before do``` in the .each? looks like it does not work since it will be also generated on the fly by minitest – mahatmanich Mar 25 '15 at 13:18
  • @mahatmanich, as much as your question is related, I think you need to branch it into its own question. – Justin Ko Mar 25 '15 at 13:22