Случайные данные в модульных тестах?

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

Ответ 11

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

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

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

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

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

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

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

Ответ 12

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

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

Ответ 13

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

Ответ 14

Мы просто столкнулись с этим сегодня. Я хотел псевдослучайный (так что это выглядело бы как сжатые аудиоданные с точки зрения размера). Я TODO'd, что я также хотел детерминированный. Ранг() отличался от OSX, чем от Linux. И если я не заново сеял, он может измениться в любое время. Поэтому мы изменили его на детерминированное, но все же psuedo-random: тест повторяем, так же как и использование консервированных данных (но более удобно написанное).

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

Означает ли это, что все еще квалифицировано, является случайным? Позвольте говорить о пиве.: -)

Ответ 15

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

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

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

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

Ответ 16

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

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

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