У меня есть класс, который предназначен для генерации случайного пароля длины, которая также случайна, но ограничена, чтобы быть между определенной минимальной и максимальной длиной.
Я создаю модульные тесты и столкнулся с интересной небольшой проблемой с этим классом. Вся идея модульного теста заключается в том, что он должен быть повторяемым. Если вы запускаете тест сто раз, он должен давать те же результаты сто раз. Если вы зависите от какого-либо ресурса, который может присутствовать, а может и не быть, или может быть, а может и не находиться в исходном состоянии, которое вы ожидаете, тогда вы должны высмеять этот ресурс, чтобы убедиться, что ваш тест действительно всегда повторяется.
Но что делать в случаях, когда SUT должен генерировать неопределенный результат?
Если я установлю минимальную и максимальную длину на одно и то же значение, тогда я могу легко проверить, что сгенерированный пароль имеет ожидаемую длину. Но если я укажу диапазон допустимых длин (скажем, 15–20 символов), то у вас теперь есть проблема, что вы можете выполнить тест сто раз и получить 100 проходов, но при 101-м запуске вы можете получить обратно 9-символьную строку.
В случае с классом пароля, который по своей сути довольно прост, он не должен вызывать огромных проблем. Но это заставило меня задуматься об общем случае. Какую стратегию обычно принимают в качестве наилучшей стратегии при работе с ТРИ, которые генерируют неопределенный выход по проекту?
источник
Ответы:
«Недетерминированный» результат должен иметь возможность стать детерминированным для целей модульного тестирования. Один из способов справиться со случайностью - это разрешить замену случайного движка. Вот пример (PHP 5.3+):
Вы можете создать специальную тестовую версию функции, которая возвращает любую последовательность чисел, которую вы хотите, чтобы убедиться, что тест полностью повторяется. В реальной программе вы можете иметь реализацию по умолчанию, которая может быть запасной, если не переопределена.
источник
Фактический выходной пароль не может быть определен при каждом выполнении метода, но он все равно будет иметь определенные функции, которые можно протестировать, такие как минимальная длина, символы, попадающие в определенный набор символов и т. Д.
Вы также можете проверить, что подпрограмма каждый раз возвращает определенный результат, каждый раз заполняя ваш генератор паролей одним и тем же значением.
источник
Тест на «контракт». Когда методы определены как «генерирует пароли длиной от 15 до 20 символов с помощью az», проверьте это следующим образом
Дополнительно вы можете извлечь генерацию, так что все, что зависит от нее, может быть протестировано с использованием другого «статического» класса генератора.
источник
У вас есть
Password generator
и вам нужен случайный источник.Как вы указали в вопросе, a
random
делает недетерминированный вывод, поскольку это глобальное состояние . Это означает, что он обращается к чему-то вне системы, чтобы генерировать ценности.Вы никогда не сможете избавиться от чего-то подобного для всех ваших классов, но вы можете отделить генерацию пароля для создания случайных значений.
Если вы структурируете код таким образом, вы можете создать макет
RandomSource
для своих тестов.Вы не сможете на 100% протестировать,
RandomSource
но предложения, которые вы получили для тестирования значений в этом вопросе, могут быть применены к нему (как тестирование, котороеrand->(1,26);
всегда возвращает число от 1 до 26).источник
В случае физики элементарных частиц Монте-Карло я написал «модульные тесты» {*}, которые вызывают недетерминированную процедуру с заранее установленным случайным начальным числом, а затем запускают статистическое число раз и проверяют нарушения ограничений (энергетические уровни выше входная энергия должна быть недоступна, все проходы должны выбирать некоторый уровень и т. д.) и регрессии против ранее записанных результатов.
{*} Такой тест нарушает принцип «сделай тест быстрым» для модульного тестирования, так что ты можешь чувствовать себя лучше, охарактеризовав его другим способом: например, приемочные тесты или регрессионные тесты. Тем не менее, я использовал мою систему модульного тестирования.
источник
Я должен не согласиться с принятым ответом по двум причинам:
(Обратите внимание, что это может быть хорошим ответом во многих обстоятельствах, но не во всех, и, возможно, не в большинстве.)
Так что я имею в виду под этим? Ну, под переоснащением я подразумеваю типичную проблему статистического тестирования: переоснащение происходит, когда вы проверяете стохастический алгоритм на чрезмерно ограниченном наборе данных. Если вы затем вернетесь и уточните свой алгоритм, вы неявно сделаете так, чтобы он очень хорошо подходил к обучающим данным (вы случайно подгоняли свой алгоритм к тестовым данным), но все остальные данные могут вообще не быть (потому что вы никогда не тестировали по нему) ,
(Между прочим, это всегда проблема, которая скрывается за модульным тестированием. Вот почему хорошие тесты завершены или, по крайней мере, репрезентативны для данного модуля, и это в общем сложно.)
Если вы делаете свои тесты детерминированными, делая подключаемый генератор случайных чисел, вы всегда проводите тестирование на одном и том же очень маленьком и (обычно) нерепрезентативном наборе данных. Это искажает ваши данные и может привести к смещению в вашей функции.
Второй момент, неосуществимость, возникает, когда вы не имеете никакого контроля над стохастической переменной. Это обычно не происходит с генераторами случайных чисел (если вам не нужен «реальный» источник случайных чисел), но это может произойти, когда стохастик проникнет в вашу проблему другими способами. Например, при тестировании параллельного кода: условия гонки всегда стохастические, вы не можете (легко) сделать их детерминированными.
Единственный способ повысить уверенность в таких случаях - это много тестировать . Вспенить, промыть, повторить. Это повышает достоверность до определенного уровня (в этот момент компромисс для дополнительных тестовых прогонов становится незначительным).
источник
У вас действительно есть несколько обязанностей здесь. Модульное тестирование и, в частности, TDD отлично подходит для освещения такого рода вещей.
Обязанности:
1) Генератор случайных чисел. 2) Форматирование пароля.
Модуль форматирования пароля использует генератор случайных чисел. Вставьте генератор в ваш форматер через его конструктор в качестве интерфейса. Теперь вы можете полностью протестировать свой генератор случайных чисел (статистический тест) и протестировать форматтер, введя имитированный генератор случайных чисел.
Вы получаете не только лучший код, но и лучшие тесты.
источник
Как уже упоминали другие, вы тестируете этот код, удаляя случайность.
Возможно, вы также захотите провести тест более высокого уровня, который оставит генератор случайных чисел на месте, протестирует только контракт (длина пароля, разрешенные символы и т. Д.) И, в случае сбоя, сбросит достаточно информации, чтобы позволить вам воспроизвести систему в одном случае указать случайный тест.
Неважно, что сам тест не повторяется - до тех пор, пока вы можете найти причину, по которой он не прошел этот раз.
источник
Многие трудности модульного тестирования становятся тривиальными, когда вы реорганизуете код, чтобы разорвать зависимости. База данных, файловая система, пользователь или, в вашем случае, источник случайности.
Другой способ взглянуть на это состоит в том, что модульные тесты должны отвечать на вопрос «выполняет ли этот код то, что я намереваюсь сделать?». В вашем случае вы не знаете, что намереваетесь сделать код, потому что он недетерминирован.
Имея это в виду, разделите свою логику на маленькие, легко понимаемые, легко проверяемые в изоляции части. В частности, вы создаете отдельный метод (или класс!), Который принимает источник случайности в качестве входных данных и выдает пароль в качестве выходных данных. Этот код явно детерминирован.
В своем модульном тесте вы каждый раз вводите один и тот же не совсем случайный ввод. Для очень маленьких случайных потоков просто закодируйте значения в вашем тесте. В противном случае, предоставьте постоянное начальное значение для ГСЧ в вашем тесте.
На более высоком уровне тестирования (назовите это «принятие» или «интеграция» или что-то еще), вы позволите коду работать с истинным случайным источником.
источник
Большинство из приведенных выше ответов показывают, что издевательство над генератором случайных чисел - это путь, однако я просто использовал встроенную функцию mt_rand. Разрешение насмешек означало бы переписать класс так, чтобы генератор случайных чисел вводился во время сборки.
Или я так думал!
Одним из последствий добавления пространств имен является то, что макетирование встроенных функций PHP перешло от невероятно сложного к тривиально простому. Если SUT находится в заданном пространстве имен, тогда все, что вам нужно сделать, это определить свою собственную функцию mt_rand в модульном тесте в этом пространстве имен, и она будет использоваться вместо встроенной функции PHP на время теста.
Вот окончательный набор тестов:
Я думал, что упомяну это, потому что переопределение внутренних функций PHP - это еще одно использование для пространств имен, которое мне просто не пришло в голову. Спасибо всем за помощь в этом.
источник
В этой ситуации следует включить дополнительный тест, который должен гарантировать, что повторные вызовы генератора паролей действительно выдают разные пароли. Если вам нужен потокобезопасный генератор паролей, вы также должны тестировать одновременные вызовы, используя несколько потоков.
Это в основном гарантирует, что вы используете вашу случайную функцию правильно, а не повторное заполнение при каждом вызове.
источник