Модульный тест должен копировать Тестовый вывод или функциональность?

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

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

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

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

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

17
задан Carl Manaster 20 March 2010 в 14:13
поделиться

7 ответов

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

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

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

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

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

4
ответ дан 30 November 2019 в 14:06
поделиться

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

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

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

1
ответ дан 30 November 2019 в 14:06
поделиться

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

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

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

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

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

9
ответ дан 30 November 2019 в 14:06
поделиться

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

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

2
ответ дан 30 November 2019 в 14:06
поделиться

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

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

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

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

1
ответ дан 30 November 2019 в 14:06
поделиться

Да. Меня это тоже беспокоит... хотя я бы сказал, что это более распространено при нетривиальных вычислениях. Чтобы избежать обновления теста при изменении кода, некоторые программисты пишут тест 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
ответ дан 30 November 2019 в 14:06
поделиться

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

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

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

0
ответ дан 30 November 2019 в 14:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: