Должен ли модульный тест воспроизводить функциональность или тестовый вывод? - PullRequest
17 голосов
/ 20 марта 2010

Я столкнулся с этой дилеммой несколько раз. Должны ли мои юнит-тесты дублировать функциональность метода, который они тестируют, чтобы проверить его целостность? ИЛИ Должны ли модульные тесты пытаться протестировать метод с многочисленными экземплярами входов и ожидаемыми выходами, созданными вручную ?

Я в основном задаю вопрос для ситуаций, когда метод, который вы тестируете, является достаточно простым и его правильную работу можно проверить, посмотрев на код в течение минуты.

Упрощенный пример (в рубине):

def concat_strings(str1, str2)
  return str1 + " AND " + str2
end

Упрощенный функционально-реплицирующий тест для вышеуказанного метода:

def test_concat_strings
  10.times do
    str1 = random_string_generator
    str2 = random_string_generator
    assert_equal (str1 + " AND " + str2), concat_strings(str1, str2)
  end
end

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

Ответы [ 7 ]

9 голосов
/ 20 марта 2010

Тестирование функциональности с использованием той же реализации, ничего не тестирует. Если у одного есть ошибка, у другого тоже будет.

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

Разновидностью этого является использование реализации, которая работает только для особых случаев. Конечно, в этом случае вы можете использовать его только для таких особых случаев.

При выборе входных значений использование случайных значений в большинстве случаев не очень эффективно. Я бы предпочел тщательно подобранные значения в любое время. В приведенном вами примере на ум приходят нулевые значения и чрезвычайно длинные значения, которые не будут вписываться в строку при объединении.

Если вы используете случайные значения, убедитесь, что у вас есть способ воссоздать точный прогон с такими же случайными значениями, например, путем регистрации начального значения и возможности установить это значение во время запуска. *

4 голосов
/ 20 марта 2010

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

Проблемаявляется то, что, поскольку алгоритм становится даже немного сложным, связь между входом и выходом становится неясной, если она представлена ​​жестко закодированными значениями.Модульный тест в конечном итоге становится постулатом .Это может работать технически, но ухудшает ремонтопригодность , потому что это приводит к неясным тестам .

Использование производных значений для проверки на результат устанавливаетнамного более четкие отношения между входным сигналом теста и ожидаемым выходным сигналом.

Аргумент, что это ничего не проверяет, просто не соответствует действительности, потому что любой тестовый пример будет выполнять только часть пути черезSUT, поэтому ни один тестовый случай не воспроизведет весь тестируемый алгоритм, но комбинация тестов сделает это.

Дополнительным преимуществом является то, что вы можете использовать меньше модульных тестов для покрытия желаемой функциональности , и даже сделать их более коммуникативными одновременно.Конечный результат - более краткие и более ремонтопригодные юнит-тесты.

2 голосов
/ 20 марта 2010

В модульном тестировании вы должны определенно вручную придумать тестовые случаи (поэтому ввод, вывод и какие побочные эффекты вы ожидаете - это будут ожидания для ваших фиктивных объектов). Вы создаете эти тестовые примеры таким образом, чтобы они охватывали все функциональные возможности вашего класса (например, охватывались все методы, все ветви всех операторов if и т. Д.). Подумайте об этом больше в духе создания документации вашего класса, показывая все возможные варианты использования.

Повторная реализация класса не является хорошей идеей, потому что вы не только получаете очевидное дублирование кода / функциональности, но также вероятно, что вы будете вносить те же ошибки в этой новой реализации.

1 голос
/ 20 марта 2010

Юнит-тест должен использовать ваш код , а не как часть языка, который вы используете.

Если логика кода заключается в особом объединении строк, вам следует проверить это - в противном случае вам нужно полагаться на свой язык / среду.

Наконец, вы должны создать свои модульные тесты так, чтобы они сначала проваливались «со смыслом». Другими словами, случайные значения не должны использоваться (если вы не проверяете, что ваш генератор случайных чисел не возвращает тот же набор случайных значений!)

1 голос
/ 20 марта 2010

для проверки функциональности метода, где бы я ни использовал пары ввода и вывода. в противном случае вы можете скопировать и вставить функциональность, а также ошибки в ее реализации. что ты тестируешь тогда? вы будете проверять, не изменилась ли функциональность (включая все ее ошибки) с течением времени. но вы не будете проверять правильность реализации.

тестирование, если функциональность не изменилась со временем, может (временно) быть полезным во время рефакторинга. но как часто вы проводите рефакторинг таких маленьких методов?

также модульные тесты можно рассматривать как документацию и как спецификацию входных данных метода и ожидаемых выходных данных. оба должны быть настолько простыми, насколько это возможно, чтобы другие могли легко их прочитать и понять. как только вы вводите дополнительный код / ​​логику в тест, его становится труднее читать.

ваш тест на самом деле выглядит как нечеткий тест . нечеткие тесты могут быть очень полезны, но в модульных тестах следует избегать случайности из-за воспроизводимости.

0 голосов
/ 21 марта 2010

Да.Это меня тоже беспокоит ... хотя я бы сказал, что это более распространено в нетривиальных вычислениях.Чтобы избежать обновления теста при изменении кода, некоторые программисты пишут тест IsX = X, который всегда выполняется независимо от SUT

  • О функции дублирования

Тебе не нужно.В вашем тесте можно указать ожидаемый результат, а не то, как вы его получили.Хотя в некоторых нетривиальных случаях это может сделать ваш тест более читабельным относительно того, как вы получили ожидаемое значение - тест как спецификацию.Вы не должны рефакторировать это дублирование

def doubler(x); x * 2; end

def test_doubler()
  input, expected = 10, doubler(10)

  assert_equal expected, doubler(10)
end

Теперь, если я заменю doubler (x) на триплер, вышеуказанный тест не пройдёт.def doubler(x); x * 3; end

Однако этот будет:

def test_doubler()
   assert_equal(20, doubler(10))
end
  • случайность в юнит-тестах - Не надо.

Вместо случайных наборов данных выберите статические репрезентативные точки данных для тестирования и используйте xUnit RowTest / TestCase для запуска теста с вводом различий.Если n входных наборов идентичны для единицы, выберите 1. Тест в OP можно использовать как предварительный тест / или для определения дополнительных репрезентативных входных наборов.Модульные тесты должны повторяться (см. Q # 61400) - Использование случайных значений побеждает эту цель.

0 голосов
/ 20 марта 2010

Никогда не используйте случайные данные для ввода. Если ваш тест сообщает об ошибке, как вы когда-нибудь сможете дублировать его? И не используйте ту же функцию для получения ожидаемого результата. Если у вас есть ошибка в вашем методе, вы, вероятно, поместите такую ​​же ошибку в свой тест. Вычислите ожидаемые результаты другим методом.

Жестко закодированные значения идеально подходят, и убедитесь, что входные данные выбраны для представления всех нормальных и граничных случаев. По крайней мере, проверьте ожидаемые входные данные, а также входные данные в неправильном формате или неправильном размере (например, нулевые значения).

Это действительно довольно просто - модульный тест должен проверить, работает функция или нет. Это означает, что вам нужно предоставить диапазон известных входных данных, которые имеют известные выходные данные, и проверить их. Универсального правильного способа сделать это не существует. Однако использование одного и того же алгоритма для метода и проверки ничего не доказывает, кроме того, что вы искусны в копировании / вставке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...