Большинство наших модульных тестов работают.
Они могут выглядеть примерно так:
class Person(object): def __init__(self, name): self.name = name def create_person(name): return Person(name) def test_func(): assert create_person('tom').name == 'tom'
Это довольно безобидно. Мы предоставили нашему невинному классу некоторую информацию для создания нового экземпляра, и запуск теста оправдал наши ожидания. Это будет просто прекрасно.
Мы хотим провалить этот тест. Черт возьми, ты хочешь, чтобы этот тест провалился. Это может показаться нелогичным, и никому не нравится этот красный текст, почти смеющийся над вами: «ха-ха, твои тесты провалились, идиот!»
Но мы хотим знать, как создание нашего невинного и чистого класса Person
может пойти ужасно, ужасно неправильно. Почему? Что ж, возможно, в этом весь смысл тестирования программного обеспечения. Подойдем к этому с другой стороны.
Допустим, вы спроектировали автомобиль. Он выглядит стильно, едет быстро и имеет удобные ковшеобразные сиденья. Прохладный! Вы заработаете миллионы.
Единственная проблема заключается в том, что если вы повернете ручку громкости на стереосистеме, регулируя правое зеркало и немного приподняв сиденье, задние шины отвалятся. Крайний срок выпуска — завтра (конечно, ради спора. Я уверен, что автомобили не так просто выпустить в дикую природу. Я был бы обеспокоен, если бы это было так).
Что вы скажете водителям-испытателям?
Пожалуйста, не используйте ручку регулировки громкости, регулируя правое зеркало и слегка приподнимая сиденье.
Именно так мы пишем модульные тесты как разработчики. Мы определяем исходные данные, которые одобряем подсознательно (или, возможно, даже сознательно?), а затем радуемся и празднуем, когда видим зеленый текст, высвобождающий эндорфин, сообщающий нам, что наши тесты пройдены.
Как программисты, мы обычно ищем автоматизированный способ делать все, поэтому вполне естественно, что кто-то (или в мире открытого исходного кода, чья-то группа) придумал способ автоматизировать создание комплексных тестовых случаев. .
Введите Гипотеза. Это заканчивается решением обеих наших проблем:
- Придумать массу тестовых случаев
- Найти что-то, что сломает тест
Гипотеза не только сделает это за вас, но, что важно, она найдет кратчайший вход, который сломает тест, и особенно то, о чем человек на самом деле не подумал бы.
Хорошим примером является поле электронной почты в веб-приложении. Ниже приведен пример кода, который проверяет это с помощью Hypothesis:
Как вы можете видеть, у нас есть функция прямой проверки, которая просто сопоставляет строку с некоторым регулярным выражением, чтобы проверить, соответствует ли она форме «[email protected]», и тест, чтобы проверить, что это работает.
В основном декоратор затем выбирает стратегию для использования. Это говорит Pytest, запустите этот тест для каждой возможной комбинации, указанной в emails()
strategy (здесь есть целый ряд различных встроенных стратегий). Запускаем тест и... он провалился!
Я позволю выходу консоли (хотя и сокращенному для краткости!) описать, что сделала Hypothesis и как она сломала тест:
[email protected] [email protected] ... [email protected] ... [email protected] ... +[email protected] +BX%[email protected] +BX%[email protected] +[email protected] +BX|[email protected] +BX|[email protected] ... +[email protected] +[email protected] +[email protected] +[email protected] ... *@a.com [email protected] [email protected] ... [email protected] [email protected] [email protected] ---------------------------------- Hypothesis ---------------------------------- Falsifying example: test_email_validation(email='[email protected]')
Мы видим, что Hypothesis обнаружил тестовый пример, который сломал функцию проверки, а затем сократил ее до самой сути. В этом случае это был символ, который не был разрешен нашим регулярным выражением, что привело к сбою теста.
Решение в нашей функции проверки состоит в том, чтобы быть более снисходительным к вводу электронной почты, поскольку стратегия emails()
соответствует RFC для действительных адресов электронной почты! Например, более простое регулярное выражение, такое как r'[^@]+@[^@]+\.[^@]+'
, приведет к прохождению теста.
Используя пример неясного сбоя в нашей недавно разработанной машине, хотя мы знаем, что «[email protected]» будет работать, мы не обязательно ожидаем, что у кого-то будет адрес электронной почты «[email protected]». , несмотря на то, что он совершенно действителен. Таким образом, гипотеза исключает человеческий фактор из тестирования и позволяет нам сосредоточиться на надежности нашего кода. В конце концов, это, пожалуй, основная причина, по которой мы тестируем отдельные устройства.
В Reposit мы используем Hypothesis для тестирования создания объектов в Django. Например, если у нас есть модель с полем name
, мы можем запустить для нее Hypothesis, чтобы увидеть, вызывает ли определенный символ или серия символов какие-либо проблемы или проблемы, о которых мы, возможно, не думали. Это особенно полезно при интеграционном тестировании, когда вы хотите знать, как другие ваши службы справляются со странным поведением на своей периферии.
Я надеюсь вскоре написать еще один пост о нашем интеграционном тестировании и о том, как вы действительно можете получить максимальную отдачу от pytest не только для модульного тестирования.
Так почему бы не попробовать? Что тебе терять?