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

136

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

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

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

Другой сотрудник добавил:

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

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

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

Адам V
источник
32
«случайные значения означают, что тест не является действительно повторяемым» не соответствует действительности, так как теты будут использовать псевдослучайные числа. Обеспечить одинаковое начальное начальное число, получить ту же последовательность «случайных» тестов.
Raedwald
11
Анекдот: Однажды я написал класс экспорта CSV, и случайное тестирование выявило ошибку, когда контрольные символы были размещены в конце ячейки. Без случайного тестирования я бы никогда не подумал добавить это в качестве контрольного примера. Это всегда терпело неудачу? Это идеальный тест? Это помогло мне поймать и исправить ошибку? Да.
Tyzoid
1
Тесты также могут служить документацией для объяснения того, когда код ожидает ввода и что ожидает вывода. Наличие теста с четкими произвольными данными может быть проще и более объяснительным, чем код, генерирующий случайные данные.
сплинтор
Если ваш модульный тест не пройден из-за случайно сгенерированного значения, и это значение не является частью утверждения, удачи в отладке вашего модульного теста.
eriksmith200

Ответы:

72

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

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

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

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

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

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

Удачного тестирования!

Apocalisp
источник
1
+1 к этому. ScalaCheck делает феноменальную работу по генерации минимизированных случайных тестовых данных повторяемым способом.
Даниэль Спивак
17
Это не случайно. Это произвольно. Большая разница :)
Apocalisp
reductiotest.org теперь, кажется, больше не существует, и Google не указал мне больше нигде. Есть идеи, где это сейчас?
Raedwald
Теперь это часть функциональной библиотеки Java. Ссылка отредактирована. Но я бы просто использовал Scalacheck для тестирования кода Java.
Apocalisp
ScalaCheck переехал в GitHub. Обновлена ​​ссылка в ответе. Это также полезно для Java, а не только для Scala. (Старая ссылка была code.google.com/p/scalacheck )
RobertB
38

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

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

Серебряный Дракон
источник
26

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

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

Уилл Дин
источник
4
Я видел эту работу хорошо в системе, которая имела много блокировок и потоков. «Случайное» начальное число записывалось в файл при каждом запуске, а затем, если запуск не удался, мы могли бы определить путь, по которому прошел код, и написать рукописный модульный тест для того случая, который мы пропустили.
Ян Рингроз
Что означает PRNG?
Системович
Генератор
Уилл Дин
16

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

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

mreggen
источник
16

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

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

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

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

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

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

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

BeeOnRope
источник
14

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

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

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

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

Джимми Боссе
источник
10

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

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

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

EBGreen
источник
9

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

Роберт Гулд
источник
2
но разве это не принципиально отличается от юнит-тестов? и сделано в другое время?
эндолит
1
@endolith нет закона физики, заставляющего вас запускать определенные тесты в определенное время
user253751
1
@immibis Но есть веские причины для проведения определенных тестов в определенное время. Вы не запускаете батарею модульных тестов каждый раз, когда пользователь нажимает кнопку «ОК».
эндолит
5

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

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

Outlaw Programmer
источник
5

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

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

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

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

craigb
источник
0

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

EBGreen
источник
0

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

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

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

Тим Джеймс
источник
0

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

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

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

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

Том W
источник
-2

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

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

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

Грег Уитфилд
источник
3
Можно записать случайные данные или случайное начальное число, чтобы можно было воспроизвести тест.
CBP