Использование Ruby и Minitest, как мне запустить тот же тестовый файл с разными данными, управляемый только списком

У меня есть код Ruby 2.0, который работает с телефонными номерами, которые я хочу протестировать с помощью MiniTest. У меня есть функция, которая принимает аргументы номера телефона и тестирует его (включая утверждения). Каждый раз, когда я вызываю эту функцию, я хочу, чтобы это был новый тестовый пример. Что-то вроде этого:

listOfPhoneNumbersForTesting.each { |phone|   testphone phone }  

Я НЕ хочу:

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

... повторить 10, 20 или 100 раз, чтобы проверить каждый номер телефона...

Очевидно, что я могу поместить код цикла в MiniTest:: Unit:: TestCase, но это приводит к одному тестовому тесту, независимо от того, сколько телефонных номеров я тестирую, и мне это не нравится. (Кроме того, если одно из утверждений терпит неудачу, то больше нет телефонных номеров, и я не хочу этого!) Также вторая форма выглядит как нарушение DRY для меня, поскольку имя класса, имя функции и аргумент все номера содержат номер телефона.

Как-то я чувствую, что должен иметь один класс под названием TestPhone и создать его с аргументом номера телефона и получить его в рамках MiniTest. но я был бы готов использовать setup(), Fixtures, метапрограммирование или что-то еще, если бы это сработало.

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

Где TestPhone является подклассом TestCase и заканчивает вызов тестового телефона для выполнения работы.

В принципе, я хочу:  1. Один список телефонных номеров, и если я добавлю число в список, я получаю еще один TestCase для вывода отчета.  2. Если тесты, связанные с одним номером телефона, терпят неудачу, остальные все еще проверяются.  3. Все номера телефонов получают одинаковое тестирование, которое включает в себя несколько утверждений.

Большое спасибо!

Ответ 1

Вы можете динамически определять методы.

В следующем примере 6 тестов динамически создаются (2 теста для каждого из 3 тестируемых значений). Это означает, что если что-то не удается, другие тесты все еще выполняются.

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

Применимый тестовый пример дает следующие результаты:

# 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

Применяя ту же концепцию к вашим тестам, я думаю, что вы хотите:

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

Аналогичный подход может быть применен при использовании тестов spec-style:

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

ВАЖНО: Следует отметить, что вам нужно убедиться, что имя созданных методов тестирования уникально для каждого тестового примера.

Например, если вы не поместите номер телефона в имя метода, вы в конечном итоге перепишете ранее определенные методы. В конечном итоге это означает, что тестируется только последний номер телефона.

Это связано с тем, что MiniTest генерирует методы тестирования "на лету" и перезаписывает уже сгенерированные методы тестирования, в конечном счете используя только последнюю переменную .each.