Я пытаюсь справиться с модульным тестированием.
Скажем, у нас есть кубик, у которого по умолчанию число сторон равно 6 (но может быть 4, 5 сторон и т. Д.):
import random
class Die():
def __init__(self, sides=6):
self._sides = sides
def roll(self):
return random.randint(1, self._sides)
Будут ли следующие действительные / полезные юнит-тесты?
- проверить бросок в диапазоне 1-6 для 6-ти стороннего штампа
- проверить бросок 0 для 6-ти стороннего штампа
- проверить бросок 7 для 6-ти стороннего штампа
- проверить бросок в диапазоне 1-3 для 3-х стороннего штампа
- проверить бросок 0 для 3-х стороннего штампа
- проверить бросок 4 для 3-х стороннего штампа
Я просто думаю, что это пустая трата времени, так как случайный модуль существует достаточно долго, но потом я думаю, что если случайный модуль будет обновлен (скажем, я обновляю свою версию Python), то, по крайней мере, меня охватят.
Кроме того, нужно ли мне даже тестировать другие варианты бросков матрицы, например, 3 в этом случае, или это хорошо, чтобы охватить другое инициализированное состояние матрицы?
python
unit-testing
tdd
Cybran
источник
источник
Ответы:
Вы правы, ваши тесты не должны проверять, что
random
модуль выполняет свою работу; unittest должен только проверять сам класс, а не то, как он взаимодействует с другим кодом (который должен быть проверен отдельно).Конечно, вполне возможно, что ваш код использует
random.randint()
неправильно; или выrandom.randrange(1, self._sides)
вместо этого звоните, и ваш кубик никогда не выбрасывает наивысшее значение, но это будет другой тип ошибки, а не тот, который вы могли бы обнаружить с помощью юнит-теста. В этом случае вашеdie
устройство работает так, как задумано, но сама конструкция была ошибочной.В этом случае, я хотел бы использовать насмешливый , чтобы заменить на
randint()
функцию, и только проверить , что это было называется правильно. Python 3.3 и выше поставляется сunittest.mock
модулем для выполнения этого типа тестирования, но вы можете установить внешнийmock
пакет на более старые версии, чтобы получить точно такую же функциональностьС насмешками ваш тест теперь очень прост; на самом деле есть только 2 случая. Случай по умолчанию для 6-стороннего кристалла и пользовательский случай.
Существуют и другие способы временно заменить
randint()
функцию в глобальном пространстве именDie
, ноmock
модуль делает это проще всего.@mock.patch
Декоратор здесь применяется ко всем методам испытаний в тесте; каждому тестовому методу передается дополнительный аргумент,random.randint()
проверяемая функция, поэтому мы можем проверить макет, чтобы убедиться, что он действительно был вызван правильно. Вreturn_value
аргумент указывает , что возвращаемые из издеваться , когда его называют, так что мы можем проверить , чтоdie.roll()
метод действительно вернул «случайный» результат для нас.Я использовал другую лучшую практику тестирования Python: импортируйте тестируемый класс как часть теста.
_make_one
Метод делает импорт и инстанцирования работу в рамках теста , так что тест модуль все равно будет загружать даже если вы сделали ошибку синтаксиса или другую ошибку , которая будет препятствовать оригинальный модуль для импорта.Таким образом, если вы допустили ошибку в самом коде модуля, тесты все равно будут запущены; они просто потерпят неудачу, сообщая вам об ошибке в вашем коде.
Чтобы было ясно, вышеприведенные тесты чрезвычайно упрощены.
random.randint()
Например, цель не в том, чтобы проверить, что было вызвано с правильными аргументами. Вместо этого цель состоит в том, чтобы проверить, что устройство дает правильные результаты при определенных входных данных, где эти входные данные включают результаты других не тестируемых устройств. Посмеиваясь над этимrandom.randint()
методом, вы получаете контроль над еще одним вводом кода.В реальных тестах реальный код в тестируемом модуле будет более сложным; связь с входными данными, передаваемыми в API, и то, как затем вызываются другие модули, могут быть по-прежнему интересными, и насмешка даст вам доступ к промежуточным результатам, а также позволит установить значения возврата для этих вызовов.
Например, в коде, который проверяет подлинность пользователей на основе сторонней службы OAuth2 (многоэтапное взаимодействие), вы хотите проверить, передает ли ваш код нужные данные этой сторонней службе и позволяет ли вы выводить различные сообщения об ошибках, которые Сторонний сервис вернется, что позволит вам моделировать различные сценарии, не создавая полноценный сервер OAuth2 самостоятельно. Здесь важно проверить, что информация из первого ответа была правильно обработана и передана вызову второго этапа, поэтому вы хотите убедиться, что проверяемая служба вызывается правильно.
источник
randint()
, а не код вDie.roll()
.sentinel.die
например (объект sentinelunittest.mock
тоже), а затем убедитесь, что это то, что было возвращено из вашего метода roll. Это фактически позволяет только один способ реализации проверенного метода.sentinel.die
это был бы отличный способ убедиться в этом.Мартейн ответит , как бы вы это сделали, если бы вы действительно хотели запустить тест, который показывает, что вы звоните random.randint. Однако, рискуя получить ответ «это не отвечает на вопрос», я чувствую, что это вообще не должно быть модульным тестированием. Дразнящий randint больше не черный ящик тестирования - вы определенно показывает , что некоторые вещи происходят в реализации . Тестирование черного ящика - это даже не вариант - нет теста, который вы можете выполнить, который докажет, что результат никогда не будет меньше 1 или больше 6.
Вы можете издеваться
randint
? Да, ты можешь. Но что ты доказываешь? Это вы назвали это аргументами 1 и сторонами. Что , что среднее? Вы вернулись на круги своя - в конце дня вам придется доказать - формально или неформально - что вызовrandom.randint(1, sides)
правильно реализует бросок костей.Я все для модульного тестирования. Они являются фантастическими проверками работоспособности и выявляют наличие ошибок. Тем не менее, они никогда не смогут доказать свое отсутствие, и есть вещи, которые вообще не могут быть подтверждены тестированием (например, что конкретная функция никогда не вызывает исключение или всегда завершается.) В этом конкретном случае я чувствую, что вы очень мало чего стоите усиление. Для поведения, которое является детерминированным, модульные тесты имеют смысл, потому что вы действительно знаете, какой ответ вы ожидаете получить.
источник
random.randint
вызывается с помощью1, sides
, бесполезна, если это неправильно.random.randint()
будет правильно возвращать значения в диапазоне [1, стороны] (включительно), это зависит от разработчиков Python, чтобы убедиться, чтоrandom
модуль работает правильно.random.randint()
вести себя какrandom.randrange()
и, следовательно, вызывать его с помощьюrandom.randint(1, sides + 1)
, то вы все равно утонете.Исправить случайное семя. Для 1, 2, 5 и 12-гранных костей подтвердите, что несколько тысяч бросков дают результаты, включая 1 и N, и не включая 0 или N + 1. Если, по всей вероятности, вы получите набор случайных результатов, которые не дают охватить ожидаемый диапазон, переключиться на другое семя.
Инструменты издевательства - это круто, но если они позволяют вам что-то делать, это еще не значит, что это нужно делать. YAGNI относится как к тестовым приборам, так и к функциям.
Если вы можете легко тестировать с немодулированными зависимостями, вы всегда должны это делать; Таким образом, ваши тесты будут направлены на уменьшение количества дефектов, а не только на увеличение количества тестов. Чрезмерные насмешливые риски приводят к вводящим в заблуждение цифрам покрытия, что, в свою очередь, может привести к переносу фактического тестирования на более поздний этап, к которому у вас, возможно, никогда не будет времени вернуться
источник
Что делать,
Die
если вы думаете об этом? - не более, чем обертка вокругrandom
. Он инкапсулируетrandom.randint
и relabels его с точки зрения собственного словарного запаса вашего приложения:Die.Roll
.Я не считаю уместным вставлять еще один уровень абстракции между собой,
Die
иrandom
потому чтоDie
сам по себе уже является этим уровнем перенаправления между вашим приложением и платформой.Если ты хочешь получить результаты игры в кости, просто издевайся
Die
, не издевайсяrandom
.В общем, я не тестирую модули-обертки, которые взаимодействуют с внешними системами, я пишу интеграционные тесты для них. Вы можете написать пару из них,
Die
но, как вы указали, из-за случайной природы базового объекта они не будут иметь смысла. Кроме того, здесь не требуется конфигурация или сетевое взаимодействие, так что тестировать особо нечего, кроме вызова платформы.=> Учитывая, что
Die
это всего лишь несколько тривиальных строк кода и мало что добавляет к логике по сравнению сrandom
самим собой, я бы пропустил тестирование в этом конкретном примере.источник
Насколько я вижу, заполнение генератора случайных чисел и проверка ожидаемых результатов НЕ является действительным тестом. Это делает предположения относительно того, КАК ваша игра в кости внутренне, которая является непослушной. Разработчики python могут изменить генератор случайных чисел или кубик (ПРИМЕЧАНИЕ: «кубик» - это множественное число, «кубик» - единственное число. Если ваш класс не реализует несколько бросков кубика за один вызов, его, вероятно, следует назвать «кубик») использовать другой генератор случайных чисел.
Точно так же насмешка над случайной функцией предполагает, что реализация класса работает точно так, как ожидалось. Почему это не так? Кто-то может взять под контроль генератор случайных чисел по умолчанию на python, и во избежание этого будущая версия вашего кубика может выбрать несколько случайных чисел или большее число случайных чисел, чтобы смешать больше случайных данных. Аналогичная схема использовалась создателями операционной системы FreeBSD, когда они подозревали, что АНБ вмешивается в аппаратные генераторы случайных чисел, встроенные в ЦП.
Если бы это был я, я бы выполнил, скажем, 6000 бросков, подсчитал их и убедился, что каждое число от 1 до 6 прокручивается от 500 до 1500 раз. Я также проверил бы, что никакие числа вне того диапазона не возвращены. Я мог бы также проверить, что для второго набора из 6000 бросков при упорядочении [1..6] по порядку частоты результат будет другим (это не удастся выполнить один раз из 720 запусков, если числа случайны!). Если вы хотите быть точным, вы можете найти частоту чисел, следующих за 1, после 2 и т. Д .; но убедитесь, что ваш размер выборки достаточно большой, и у вас достаточно дисперсии. Люди ожидают, что случайные числа будут иметь меньше шаблонов, чем на самом деле.
Повторите эти действия для 12-стороннего и 2-стороннего кристалла (наиболее часто используется 6, поэтому наиболее ожидаемо для любого, кто пишет этот код).
Наконец, я хотел бы проверить, что происходит с 1-сторонним штампом, 0-сторонним штампом, -1-сторонним штампом, 2,3-сторонним штампом, [1,2,3,4,5,6] двусторонним штампом и умирающий "бла" Конечно, все они должны потерпеть неудачу; они терпят неудачу полезным способом? Вероятно, они должны потерпеть неудачу при создании, а не при переходе
Или, может быть, вы хотите обрабатывать их по-другому - возможно, создание кубика с [1,2,3,4,5,6] должно быть приемлемым - и, возможно, также "бла"; это может быть кубик с четырьмя лицами, на каждом из которых есть буква. Игра «Boggle» приходит на ум, как и волшебный шар из восьми.
И наконец, вы можете рассмотреть это: http://lh6.ggpht.com/-fAGXwbJbYRM/UJA_31ACOLI/AAAAAAAAAPg/2FxOWzo96KE/s1600-h/random%25255B3%25255D.jpg
источник
Рискнув плыть против течения, я решил эту проблему несколько лет назад, используя метод, который пока не упоминался.
Моя стратегия заключалась в том, чтобы просто издеваться над ГСЧ, который создает предсказуемый поток значений, охватывающий все пространство. Если (скажем) сторона = 6, и ГСЧ выдает значения от 0 до 5 в последовательности, я могу предсказать, как должен вести себя мой класс, и провести модульное тестирование соответственно.
Обоснование состоит в том, что это проверяет логику только в этом классе, исходя из предположения, что ГСЧ в конечном итоге будет производить каждое из этих значений и без проверки самого ГСЧ.
Это просто, детерминистично, воспроизводимо и ловит ошибки. Я бы снова использовал ту же стратегию.
В вопросе не прописано, какими должны быть тесты, а какие данные могут быть использованы для тестирования, учитывая наличие ГСЧ. Мое предложение - просто провести исчерпывающую проверку, издеваясь над ГСЧ. Вопрос о том, что стоит проверить, зависит от информации, не предоставленной в вопросе.
источник
Тесты, которые вы предлагаете в своем вопросе, не обнаруживают модульный арифметический счетчик как реализацию. И они не обнаруживают типичные ошибки реализации в коде, связанном с распределением вероятностей
return 1 + (random.randint(1,maxint) % sides)
. Или изменение в генераторе, которое приводит к 2-мерным образцам.Если вы действительно хотите убедиться, что вы генерируете равномерно распределенные случайные числа, вам нужно проверить очень широкий спектр свойств. Чтобы сделать достаточно хорошую работу, вы можете запустить http://www.phy.duke.edu/~rgb/General/dieharder.php на свои сгенерированные номера. Или напишите такой же сложный набор юнит-тестов.
Это не вина модульного тестирования или TDD, случайность просто очень трудно проверить. И популярная тема для примеров.
источник
Самый простой тест броска кубика - просто повторить его несколько сотен тысяч раз и проверить, что каждый возможный результат был получен примерно (1 / количество сторон) раз. В случае 6-стороннего кубика вы должны увидеть, как каждое возможное значение поражает примерно 16,6% времени. Если какие-либо из них более чем на процент, то у вас есть проблемы.
Это позволяет избежать рефакторинга базовой механики генерации случайного числа легко и, что наиболее важно, без изменения теста.
источник