Эквивалент TestCase nUnit в Ruby 1.9.2 Test::Unit

Когда я недавно решил изучить Ruby, я также решил, что начну использовать модульные тесты и методологию TDD. Я начал с Test::Unit и написал пару очень маленьких классов, чтобы получить представление о модульном тестировании в целом и о Ruby и Test::Unit в частности.

До сих пор все было довольно просто, но затем я захотел сделать что-то похожее на TestCase от nUnit, поскольку у меня было 20 тестов, в которых менялись только ввод и вывод.

Код выглядит следующим образом:

def test
[TestCase(2.5d, 2d, Result=1.25d)]
[TestCase(-2.5d, 1d, Result = -2.5d)]
public double ValidateDivision(double numerator, double denominator)
{
    var myClass = new MyClass();
    return myClass.Divide(numerator,denominator);
}
inserted_return_true actual = @prime_generator.is_prime?(2) assert_equal(true, actual) end def test_3_inserted_return_true actual = @prime_generator.is_prime?(3) assert_equal(true, actual) end def test_5_inserted_return_true actual = @prime_generator.is_prime?(5) assert_equal(true, actual) end

Что довольно ужасно с DRY-перспективы. Я бы хотел что-то похожее на TestCase от nUnit.

Что-то вроде этого:

[TestCase(2.5d, 2d, Result=1.25d)]
[TestCase(-2.5d, 1d, Result = -2.5d)]
public double ValidateDivision(double numerator, double denominator)
{
    var myClass = new MyClass();
    return myClass.Divide(numerator,denominator);
}

Я пробовал гуглить, но ничего не нашел о Test::Unit. Я нашел кое-что в RSpec и Selenium, но это мне не очень помогает. Я тоже пробовал искать здесь, но тоже ничего не нашел.

Я думал о том, чтобы метод тестирования принимал параметры, но снова определял методы... Не доволен этим. Кроме того, если я правильно помню, это было даже невозможно (сейчас я не могу проверить, а то хотел бы).

Итак, мой вопрос: возможно ли проводить тестирование на основе данных с помощью Test::Unit в Ruby (1.9.2)? Если нет, то какие фреймворки могут это сделать?


person Whettingstone    schedule 06.04.2011    source источник


Ответы (3)


Давайте не будем забывать о старых добрых циклах:

def test_that_the_first_few_primes_are_detected_as_prime
  [2, 3, 5, 7, 11, 13, 17].each do |p|
    assert @primality_tester.prime?(p)
  end
end

Некоторые люди использовали бы метапрограммирование для определения отдельных методов тестирования, но в данном случае я думаю, что это излишество:

[2, 3, 5, 7, 11, 13, 17].each do |p|
  define_method :"test_that_#{p}_is_detected_as_prime" do
    assert @primality_tester.prime?(p)
  end
end

Вообще, я не думаю, что тесты должны быть СУХИМИ. Они должны быть DAMP (описательные и осмысленные фразы). В конце концов, тесты — это ваша спецификация и ваша документация, и их читают люди, которые могут быть незнакомы с Ruby или даже с программированием в целом. Итак, я даже не уверен, что ваш исходный пример плохой, особенно если вы немного подчистите его, как я сделал выше:

def test_that_2_is_detected_as_prime
  assert @primality_tester.prime?(2)
end

def test_that_3_is_detected_as_prime
  assert @primality_tester.prime?(3)
end

def test_thatassert_equalis_detected_as_prime
  assert @primality_tester.prime?(5)
end

Вот что я сделал:

  • Переименуйте тесты. Теперь название теста образует законченное предложение, а не просто переформулирует то, что тест делает.
  • Используйте assert вместо assert_equal: проверка на равенство с true в любом случае почти никогда не бывает хорошей идеей, а assert уже проверяет, что результат правдив, так зачем беспокоиться?
  • Переименуйте @prime_generator в @primality_tester, так как он не генерирует простые числа, а только проверяет, является ли число простым.
  • Переименуйте is_prime? просто в prime?, так как вопрос уже подразумевается знаком вопроса
  • И последнее, но не менее важное: исправьте форматирование, поскольку форматирование не только не соответствовало стандартным соглашениям о кодировании Ruby, но и не соответствовало самому себе.

Однако лучшее решение IMO будет примерно таким:

def test_that_the_first_few_primes_are_detected_as_prime
  assert @primality_tester.prime?(2)
  assert @primality_tester.prime?(3)
  assert @primality_tester.prime?(5)
end

Там есть дублирование, но именно дублирование необходимо для того, чтобы тест был читабельным и произносимым. DRY — хороший принцип для производственного кода, который развивается и расширяется, но для тестов DRY всегда нужно балансировать с DAMP. (Возможно, было бы разумно написать собственное утверждение, но я сомневаюсь в этом. По крайней мере, я не могу придумать для него подходящее имя, что всегда является подсказкой.)

Совершенно другим подходом может быть проверка на основе свойств, аналогичная QuickCheck в Haskell или Pex в .NET. Существует версия QuickCheck для Ruby под названием RushCheck, но она не поддерживается с 2006 года, а на техническое обслуживание устранена лишь пара проблем. недель назад, и еще предстоит проделать большую работу, чтобы ускорить работу с последними версиями Ruby.

person Jörg W Mittag    schedule 06.04.2011
comment
Как я уже сказал, я все еще новичок как в Ruby, так и в модульном тестировании. Очень хорошие предложения. Это был именно тот материал, который я искал. Действительно очень полезно. - person Whettingstone; 07.04.2011

Вы должны проверить rspec для модульного тестирования:

require 'prime'

describe "Numbers List" do
  [2, 3, 5, 7, 11, 13, 17].each do |i|
    it "#{i} is prime" do
      i.prime?
    end
  end
end

текущая доходность:

scratch-1.9.2> rspec -cfn primes.rb 

Numbers List
  2 is prime
  3 is prime
  5 is prime
  7 is prime
  11 is prime
  13 is prime
  17 is prime

Finished in 0.00146 seconds
7 examples, 0 failures
person Wes    schedule 06.04.2011
comment
Я рассмотрю как rspec, так и другие фреймворки, но подожду некоторое время, пока не изучу модульное тестирование и Test::Unit лучше, однако спасибо за предложение. - person Whettingstone; 07.04.2011

Я сделал это с помощью метапрограммирования, используя несколько примеров, которые нашел в блоге Джея Филдса. Сработало отлично, но вызов def_each нужно было сделать в первой строке моего тестового файла. Надеюсь, этот пример кому-то поможет, хотелось бы увидеть этот ответ, но узнать о define_method было довольно круто :)

def self.def_each(method_names, &block)
 method_names.each do |method_name|
  method_name_fixed="test_"+method_name     
  define_method method_name_fixed do
    instance_exec method_name, &block
   end
 end
end

@@servers = ["pixeltmp01.fetchback.com","pixeltmp02.fetchback.com","pixeltmp03.fetchback.com"]

# generate test methods using fixtures and any other testcase-specific data
def_each @@servers do |method_name|
 @fb.withPixelServer(method_name)
 self.LandingPixelM
end
person chollaball    schedule 14.05.2013