Вопрос

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

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

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

Другой коллега добавил:

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

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

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

Это было полезно?

Решение

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

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

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

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

Java http://functionaljava.org/

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

Хаскелл http://www.cs.chalmers.se /~rjmh/Быстрая проверка/

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мы столкнулись с этим только сегодня.Я хотел псевдослучайный (таким образом, по размеру это было бы похоже на сжатые аудиоданные).Я СДЕЛАЛ то, чего я тоже хотел детерминированный.rand() в OSX отличался от Linux.И если я не проведу повторный посев, все может измениться в любой момент.Поэтому мы изменили его на детерминированный, но все еще псевдослучайный:тест повторяем в той же степени, что и при использовании готовых данных (но в более удобном виде).

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

Это все еще считается случайным?Давай поговорим за пивом.:-)

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

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

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

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

Как ваш сотрудник может снова запустить тест, если он не смог проверить, исправил ли он это?То есть.он теряет повторяемость тестов.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top