Случайные данные в модульных тестах? - PullRequest
129 голосов
/ 28 августа 2008

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

Я привел ему несколько разных причин против этого, главные из которых:

  • случайные значения означают, что тест на самом деле не повторяется (что также означает, что если тест может случайно произойти, он может сделать это на сервере сборки и прервать сборку)
  • если это случайное значение и проверка не пройдена, нам нужно: а) исправить объект и б) заставить себя каждый раз проверять это значение, поэтому мы знаем, что оно работает, но, поскольку оно случайное, мы не знаем, что значение было

Добавлен еще один сотрудник:

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

Может ли кто-нибудь еще добавить дополнительные причины, которые я могу дать ему, чтобы он прекратил это делать?

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

Ответы [ 16 ]

69 голосов
/ 28 августа 2008

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

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

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

Я не знаю, какой язык вы используете, но смотрите здесь:

Java http://functionaljava.org/

Scala (или Java) http://github.com/rickynils/scalacheck

Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

.NET: http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

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

Счастливого тестирования!

36 голосов
/ 18 сентября 2008

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

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

22 голосов
/ 28 августа 2008

Здесь есть дом на полпути, который имеет какое-то применение, который состоит в том, чтобы заполнять ваш PRNG константой. Это позволяет генерировать «случайные» данные, которые можно повторять.

Лично я думаю, что есть места, где (постоянные) случайные данные полезны при тестировании - после того, как вы думаете, что вы сделали все свои тщательно продуманные углы, использование стимулов от PRNG иногда может найти другие вещи.

12 голосов
/ 28 августа 2008

В книге Красивый код есть глава под названием "Красивые тесты", где он проходит стратегию тестирования алгоритма Бинарный поиск . Один абзац называется «Случайные акты тестирования», в котором он создает случайные массивы для тщательного тестирования алгоритма. Вы можете прочитать об этом в Интернете в Google Книгах, страница 95 , но это хорошая книга, которую стоит иметь.

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

10 голосов
/ 20 декабря 2010

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

9 голосов
/ 14 июня 2013

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

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

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

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

8 голосов
/ 28 августа 2008
  • если это случайное значение и проверка не пройдена, нам нужно а) исправить объект и б) заставить себя каждый раз проверять это значение, поэтому мы знаем, что оно работает, но поскольку оно случайное, мы не знаем значение было

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

7 голосов
/ 16 июня 2016

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

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

Многие из ваших аргументов против рандомизированных данных являются разновидностями "теста не воспроизводится". Однако хорошо написанный рандомизированный тест захватит начальное число, использованное для запуска рандомизированного начального значения, и выдаст его при неудаче. В дополнение к тому, что вы можете повторить тест вручную, это позволяет вам тривиально создать новый тест, который тестирует конкретную проблему, жестко закодировав начальное значение этого теста. Конечно, было бы лучше написать вручную явный тест, охватывающий этот случай, но лень имеет свои достоинства, и это даже позволяет вам автоматически генерировать новые тестовые случаи из неудачного начального числа.

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

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

Локально, например, при изменении соответствующего класса вы можете запускать его для дополнительных итераций или с другими начальными значениями. Если рандомизированное тестирование становится все более популярным, вы можете даже представить конкретный набор тестов, которые, как известно, являются случайными, которые могут запускаться с различными начальными значениями (следовательно, с увеличением охвата с течением времени), и где сбои не означают одно и то же как детерминированные системы CI (т. е. прогоны не связаны 1: 1 с изменениями кода, поэтому вы не указываете на конкретное изменение в случае неудачи).

Многое можно сказать о рандомизированных тестах, особенно хорошо написанных, поэтому не спешите их отклонять!

7 голосов
/ 13 марта 2009

Ваш коллега проводит фазз-тестирование , хотя он не знает об этом. Они особенно ценны в серверных системах.

4 голосов
/ 06 октября 2014

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

...