Большинство наших модульных тестов работают.

Они могут выглядеть примерно так:

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 может пойти ужасно, ужасно неправильно. Почему? Что ж, возможно, в этом весь смысл тестирования программного обеспечения. Подойдем к этому с другой стороны.

Допустим, вы спроектировали автомобиль. Он выглядит стильно, едет быстро и имеет удобные ковшеобразные сиденья. Прохладный! Вы заработаете миллионы.

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

Что вы скажете водителям-испытателям?

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

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

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

Введите Гипотеза. Это заканчивается решением обеих наших проблем:

  1. Придумать массу тестовых случаев
  2. Найти что-то, что сломает тест

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

Хорошим примером является поле электронной почты в веб-приложении. Ниже приведен пример кода, который проверяет это с помощью 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 не только для модульного тестирования.

Так почему бы не попробовать? Что тебе терять?