Случайные данные в модульных тестах?
У меня есть коллега, который пишет юнит-тесты для объектов, которые заполняют свои поля случайными данными. Его причина в том, что он дает более широкий диапазон тестирования, так как он будет тестировать много разных значений, тогда как обычный тест использует только одно статическое значение.
Я дал ему несколько различных причин против этого, основные из которых являются:
- случайные значения означают, что тест действительно не повторяется (что также означает, что если тест может случайно провалиться, он может сделайте это на сервере сборки и разбейте сборку)
- если это случайное значение и тест не удается, нам нужно а) исправить объект и б) заставить себя проверять это значение каждый раз, поэтому мы знаем, что это работает, но поскольку это случайно, мы не знаем, какое значение было
другой сотрудник добавил:
- если я тестирую исключение, случайные величины не могут гарантировать, что тест заканчивается в ожидаемом состоянии
- случайные данные используются для промывки системы и нагрузочного тестирования, а не для модульных тестов
может ли кто-нибудь еще добавить дополнительные причины, которые я могу дать ему, чтобы заставить его прекратить это делать?
(или, наоборот, это приемлемый метод написания модульных тестов, и я и мой другой коллега ошибаетесь?)
16 ответов:
есть компромисс. Ваш коллега на самом деле на что-то, но я думаю, что он делает это неправильно. Я не уверен, что полностью случайное тестирование очень полезно, но это, конечно, не является недействительным.
спецификация программы (или блока) - это гипотеза о том, что существует какая-то программа, которая ей соответствует. Сама программа тогда является доказательством этой гипотезы. Что модульное тестирование должно быть это попытка представить контраргументы, чтобы опровергнуть, что программа работает в соответствии с спекуляция.
теперь вы можете написать модульные тесты вручную, но это действительно механическая задача. Его можно автоматизировать. Все, что вам нужно сделать, это написать спецификацию, и машина может генерировать множество модульных тестов, которые пытаются сломать ваш код.
Я не знаю, какой язык вы используете, но увидеть здесь:
Java http://functionaljava.org/
Scala (или Ява) 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
эти инструменты будут принимать ваши хорошо сформированные спецификации в качестве входных данных и автоматически генерировать столько модульных тестов, сколько вы хотите, с автоматически генерируемыми данными. Они используют стратегии" сокращения " (которые вы можете настроить), чтобы найти самые простые возможный тестовый случай, чтобы сломать ваш код и убедиться, что он хорошо покрывает крайние случаи.
удачного тестирования!
этот вид тестирования называется тест обезьяна. Когда все сделано правильно, он может выкурить ошибки из действительно темных углов.
чтобы решить ваши проблемы с воспроизводимостью: правильный способ подойти к этому-записать неудачные тестовые записи, создать модульный тест, который зондирует для всей семьи конкретной ошибки; и включить в модульный тест один конкретный вход (из случайных данных), который вызвал первоначальный сбой.
здесь есть дом на полпути, который имеет некоторую пользу, которая заключается в том, чтобы засеять ваш PRNG константой. Это позволяет генерировать "случайные" данные, которые можно повторить.
лично я думаю, что есть места, где (постоянные) случайные данные полезны при тестировании - после того, как вы думаете, что вы сделали все свои тщательно продуманные углы, используя стимулы из PRNG иногда можно найти другие вещи.
в книге Красивый Код, есть глава под названием "красивые тесты", где он проходит через стратегии тестирования Бинарный Поиск. Один абзац называется "случайные акты тестирования", в котором он создает случайные массивы для тщательной проверки алгоритма. Вы можете прочитать некоторые из них в интернете на Google Books,страница 95, но это отличная книга стоит иметь.
Так что в основном это просто показывает, что генерация случайных данных для тестирование является жизнеспособным вариантом.
Если вы делаете TDD, то я бы сказал, что случайные данные-отличный подход. Если ваш тест написан с константами, то вы можете гарантировать, что ваш код работает только для определенного значения. Если ваш тест случайно не работает на сервере сборки, вероятно, существует проблема с тем, как был написан тест.
случайные данные помогут гарантировать, что любой будущий рефакторинг не будет полагаться на магическую константу. В конце концов, если ваши тесты - это ваша документация, то не наличие константы подразумевают, что он должен работать только для этих констант?
я преувеличиваю, однако я предпочитаю вводить случайные данные в мой тест как знак того, что "значение этой переменной не должно влиять на результат этого теста".
Я скажу, что если вы используете случайную величину, то вилка вашего теста на основе этой переменной, то это запах.
одним из преимуществ для тех, кто смотрит на тесты, является то, что произвольные данные явно не важны. Я видел слишком много тестов, которые включали десятки фрагментов данных, и может быть трудно сказать, что должно быть таким образом, и что просто происходит таким образом. Например, если метод проверки адреса тестируется с определенным почтовым индексом, а все остальные данные случайны, вы можете быть уверены, что почтовый индекс является единственной важной частью.
- если это случайное значение и тест не удается, нам нужно а) исправить объект и б) заставить себя проверять это значение каждый раз, поэтому мы знаем, что это работает, но поскольку это случайно, мы не знаем, какое значение было
если ваш тестовый случай не точно записывает то, что он тестирует, возможно, вам нужно перекодировать тестовый случай. Я всегда хочу иметь журналы, на которые я могу ссылаться для тестовых случаев, чтобы точно знать, что вызвало его сбой будь то использование статических или случайных данных.
ваш коллега делает fuzz-тестирование, хотя он об этом не знает. Они особенно ценны в серверных системах.
Я за случайные тесты, и я их пишу. Тем не менее, являются ли они подходящими в конкретной среде сборки и в какие наборы тестов они должны быть включены, является более тонким вопросом.
запуск локально (например, в течение ночи на вашем dev box) рандомизированные тесты обнаружили ошибки как очевидные, так и неясные. Неясные из них достаточно загадочны, что я думаю, что случайное тестирование было действительно единственным реалистичным, чтобы очистить их. В качестве теста я взял одну труднодоступную ошибку, обнаруженную через рандомизированное тестирование и полдюжины разработчиков crack пересмотрели функцию (около десятка строк кода), где это произошло. Никто не смог его обнаружить.
многие из ваших аргументов против рандомизированных данных являются разновидностями "тест не воспроизводим". Однако хорошо написанный рандомизированный тест захватит семя, используемое для запуска рандомизированного семени, и выведет его при сбое. В дополнение к тому, что вы можете повторить тест вручную, это позволяет вам тривиально создать новый тест, который проверьте конкретную проблему, жестко закодировав семя для этого теста. Конечно, вероятно, лучше вручную закодировать явный тест, охватывающий этот случай, но лень имеет свои достоинства, и это даже позволяет вам автоматически генерировать новые тестовые случаи из неудачного семени.
единственное, что вы делаете, что я не могу обсуждать, однако, это то, что он ломает системы сборки. Большинство тестов сборки и непрерывной интеграции ожидают, что тесты будут делать то же самое каждый раз. Таким образом, тест, который случайно терпит неудачу создадим хаос, не случайно и указывая пальцами на изменения, которые были безвредны.
тогда решение состоит в том, чтобы все еще запускать ваши рандомизированные тесты как часть тестов build и CI, но запустите его с фиксированным семенем, для фиксированного числа итераций. Следовательно, тест всегда делает то же самое, но все еще исследует кучу входного пространства (если вы запускаете его для нескольких итераций).
локально, например, при изменении соответствующего класса, вы можете запустить это для большего количества итераций или с другими семенами. Если рандомизированное тестирование когда-либо станет более популярным, вы можете даже представить себе конкретный набор тестов, которые, как известно, являются случайными, которые могут выполняться с разными семенами (следовательно, с увеличением охвата с течением времени), и где сбои не будут означать то же самое, что и детерминированные системы CI (т. е. запуски не связаны 1:1 с изменениями кода, и поэтому вы не указываете пальцем на конкретное изменение, когда что-то терпит неудачу).
есть много, чтобы быть сказано для рандомизированных тестов, особенно хорошо написанных, поэтому не спешите их отклонять!
вы можете создать некоторые случайные данные один раз (я имею в виду ровно один раз, а не один раз за время теста), а затем использовать его во всех тестах после этого?
Я определенно вижу ценность в создании случайных данных для тестирования тех случаев, о которых вы не думали, но вы правы, иметь модульные тесты, которые могут случайно пройти или потерпеть неудачу, - это плохо.
вы должны спросить себя какова цель вашего теста.
тесты о проверке логики, потока кода и взаимодействия объектов. Использование случайных значений пытается достичь другой цели, тем самым снижает фокус теста и простоту. Это приемлемо по соображениям удобочитаемости (генерации UUID-идентификатор, идентификаторы, ключи и др.).
Специально для модульных тестов я не могу вспомнить, даже когда этот метод был успешным в поиске проблем. Но я видел много проблем детерминизма (в тестах) пытаясь быть умным со случайными значениями и в основном со случайными датами.
Тестирование является эффективным подходом для интеграционные тесты и сквозные тесты.
Если вы используете случайный ввод для своих тестов, вам нужно зарегистрировать входы, чтобы вы могли видеть, какие значения есть. Таким образом, если есть какой-то крайний случай, с которым вы сталкиваетесь, вы can написать тест, чтобы воспроизвести его. Я слышал те же причины от людей, которые не используют случайный ввод, но как только вы получите представление о фактических значениях, используемых для конкретного тестового запуска, тогда это не такая большая проблема.
понятие "произвольных" данных также очень полезно в качестве способа означая то, что есть не важно. У нас есть некоторые приемочные тесты, которые приходят на ум, когда есть много шумовых данных, которые не имеют отношения к тесту под рукой.
в зависимости от вашего объекта/приложения, случайные данные будут иметь место в нагрузочном тестировании. Я думаю, что более важным было бы использовать данные, которые явно проверяют граничные условия данных.
мы только что столкнулись с этим сегодня. Я хотел псевдослучайных (Так что это будет выглядеть как сжатые аудио данные с точки зрения размера). Я TODO'D, что я тоже хотел детерминированные. rand () отличался в OSX от Linux. И если я не пересеваю, это может измениться в любое время. Поэтому мы изменили его на детерминированный, но все же псевдослучайный: тест повторяется, как и использование консервированных данных (но более удобно записанных).
Это не тестирование с помощью некоторой случайной грубой силы через пути кода. Вот в чем разница: все еще детерминированный, все еще повторяемый, все еще использующий данные, которые выглядят как реальный ввод, чтобы запустить набор интересных проверок на крайних случаях в сложной логике. Все модульные тесты.
Это все еще квалифицируется случайным образом? Давай поговорим за пивом. : -)
Я могу предусмотреть три решения проблемы с тестовыми данными:
- тест с фиксированными данными
- тест со случайными данными
- генерировать случайные данные после, а затем использовать его в качестве фиксированных данных
Я бы рекомендовал делать все выше. То есть, напишите повторяемые модульные тесты как с некоторыми крайними случаями, разработанными с использованием вашего мозга, так и с некоторыми рандомизированными данными, которые вы генерируете только один раз. Затем напишите набор рандомизированные тесты а также.
рандомизированные тесты никогда не следует ожидать, чтобы поймать что-то ваши повторяемые тесты пропустить. Вы должны стремиться охватить все повторяемые тесты и считать рандомизированные тесты бонусом. Если они что-то найдут, это должно быть что-то, что вы не могли разумно предсказать; настоящий чудак.
Как ваш парень может запустить тест снова, когда он не смог увидеть, если он исправил его? То есть он теряет повторяемость тестов.
хотя я думаю, что, вероятно, есть некоторая ценность в том, чтобы бросить нагрузку случайных данных на тесты, Как упоминалось в других ответах, она больше подпадает под рубрику нагрузочного тестирования, чем что-либо еще. Это в значительной степени практика "тестирования по Надежде". Я думаю, что на самом деле ваш парень просто не думает о том, что он пытается проверить, и восполняет этот недостаток мысли, надеясь, что случайность в конечном итоге поймает какую-то таинственную ошибку.
поэтому аргумент, который я бы использовал с ним, заключается в том, что он ленив. Или, другими словами, если он не тратит время на то, чтобы понять, что он пытается проверить, это, вероятно, показывает, что он действительно не понимает код, который он пишет.
Comments