Используя 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 и в конечном итоге вызывает testphone для выполнения работы.

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

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


person user2773661    schedule 12.09.2013    source источник


Ответы (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

Аналогичный подход можно использовать при использовании тестов в стиле спецификации:

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.

person Justin Ko    schedule 12.09.2013
comment
Джастин, это прекрасно ответило на мой вопрос. Большое спасибо! - person user2773661; 13.09.2013
comment
Это отличное решение для параметризации тестовых случаев. Я добавляю параметризованный в качестве комментария в надежде, что люди, которые ищут в Google параметризованные тестовые примеры ruby ​​minitest (например, я за последние 2 часа), могут найти это прекрасное решение. БЛАГОДАРНОСТЬ! - person Marty Neal; 14.03.2014
comment
возможно ли это с помощью тестов в стиле спецификаций? если да, не могли бы вы привести пример? ваше здоровье - person mahatmanich; 24.03.2015
comment
@mahatmanich, да, это возможно с помощью тестов в стиле спецификаций. Как видно из добавленного примера, это аналогичный подход. - person Justin Ko; 24.03.2015
comment
Я пробовал это, но это не позволило бы .each туда? - person mahatmanich; 24.03.2015
comment
Да, я мог бы сделать это, в любом случае, спасибо за синтаксис, я попробую это завтра. Не могу проверить тест прямо сейчас :-) - person mahatmanich; 24.03.2015
comment
Я только что наткнулся на ловушку, о которой вы упомянули, я выделил ее жирным шрифтом. - person mahatmanich; 25.03.2015
comment
как бы вы сделали setup или before do в .each? похоже, что это не работает, так как он также будет генерироваться на лету минитестом - person mahatmanich; 25.03.2015
comment
@mahatmanich, насколько ваш вопрос связан, я думаю, вам нужно разветвить его на отдельный вопрос. - person Justin Ko; 25.03.2015