Предисловие
Mock — это просто объект, который создает пустой тест для определенной части программы.
Вместо вызова обычной проверки, вы вызываете mock и смотрите, как проходит тест какая-то часть программы.
Какие преимущества имеет mock?
- Высокая скорость
Быстрые тесты бывают очень полезны. Например, если у вас есть ресурсоемкая функция, mock для этой функции сократит ненужное использование ресурсов во время тестирования, тем самым сократив время выполнения теста.
- Избежание нежелательных побочных эффектов во время тестирования
Если вы тестируете функцию, которая вызывает внешний API, скорее всего вам не особо хочется каждый раз вызывать API для того, чтобы запустить тест. Вам придется менять код каждый раз, когда изменяется API, или могут быть некоторые ограничения скорости, но mock помогает этого избежать.
Что нужно, для того чтобы использовать mock?
Для начала вам нужно будет установить Python версии 3.3 или выше. Скачать Python можно здесь. Для этого урока я буду использовать Python версии 3.6.0.
После установки настройте виртуальную среду:
python3 -m venv mocking
Активируйте виртуальную среду:
source mocking/bin/activate
После этого добавьте файл main.py, в котором будет находиться наш код и файл test.py для наших тестов:
touch main.py test.py
Основы
Представьте себе самый обычный класс:
Этот класс реализует сумму двух чисел, которая принимает два аргумента, а и b, а возвращает a + b;
Простой тест для этого класса может быть таким:
Вы можете запустить этот тест, используя команду:
python -m unittest
На выходе вы увидите что-то похожее на это:
Довольно быстро, не так ли?
Теперь представьте себе код, который выглядит вот так:
Так как это простой пример, мы используем time.sleep() для имитации продолжительного процесса. Предыдущий тест теперь выдает следующее:
Этот процесс существенно замедлил наши тесты. Это явно не самая лучшая идея — вызывать sum каждый раз, когда мы проводим тесты. Это ситуация, когда мы можем использовать mock, чтобы ускорить тесты и избежать нежелательных последствий.
Перепишем тест так, чтобы вместо того, чтобы называть sum каждый раз, когда выполняется тест, мы будем вызывать mock функции sum с определенным поведением.
Мы импортируем декоратор patch из unittest.mock. Он заменяет фактическую функцию sum ложной функцией, которая ведет себя именно так, как мы хотим. В этом случае наша фиктивная функция всегда возвращает 9. В течение всего теста функция sum заменяется на mock. Запустив этот тест, мы получаем такие выходные данные:
Сначала это может показаться интуитивно понятным, помните, что mock позволяет обеспечить так называемую поддельную реализацию той части вашей программы, которую вы тестируете. Это дает вам больше «гибкости» во время проведения тестов.
Более продвинутый пример использования
В этом примере мы будем использовать библиотеку requests, чтобы мы могли вызывать API. Вы можете установить ее используя pip install
pip install requests
Наш код под тест в main.py выглядит следующим образом:
Этот код определяет класс Blog с методом posts. Запустив posts в Blog, вы инициируете вызов API. Ссылаясь на post в Blog, объект будет инициировать вызов API jsonplaceholder.
В данном тесте мы хотим имитировать непредвиденный вызов API и проверить, что функция posts объекта Blog возвращает posts. Нам нужно будет исправить все posts объекта Blog следующим образом.
'body': 'Far out in the uncharted backwaters of the unfashionable end of the western spiral arm of the Galaxy\ lies a small unregarded yellow sun.'
}
]
Вы можете обратить внимание, что функция test_blog_posts украшена декоратором (часть 1, часть 2) @patch. Когда функция оформлена через @patch, mock класса, метода или функции, переданная в качестве цели для @patch, возвращается и передается в качестве аргумента декорируемой функции.
В этом случае @patch вызывается с помощью main.Blog и возвращает mock, который передается функции теста как MockBlog. Важно отметить, что цель, перешедшая к @patch, должна быть импортирована в @patch, из которой она была вызвана. В нашем случае импорт формы from main import Blog должен быть разрешен без каких либо проблем.
Кроме того, обратите внимание, что MockBlog является обычной переменной и вы можете назвать её, как хотите.
Вызов blog.posts() на нашем ложном объекте блога возвращает предопределенный JSON.
Обратите внимание, что тестирование mock вместо фактического объекта блога, позволяет нам делать дополнительные утверждения о том, как mock использовался.
Например, mock позволяет проверить, сколько раз он вызывался, аргументы, с которыми он вызывался, и даже был ли mock вообще когда либо вызван. Дополнительные примеры мы увидим в следующем разделе.
Другие примеры, как мы можем использовать mock
Используя предыдущий пример, мы можем сделать несколько полезных утверждений о нашем объекте mock blog.
# Additional assertions
assert MockBlog is main.Blog # The mock is equivalent to the original
assert MockBlog.called # The mock wasP called
blog.posts.assert_called_with() # We called the posts method with no arguments
blog.posts.assert_called_once_with() # We called the posts method once with no arguments
# blog.posts.assert_called_with(1, 2, 3) - This assertion is False and will fail since we called blog.posts with no arguments
blog.reset_mock() # Reset the mock object
blog.posts.assert_not_called() # After resetting, posts has not been called.
Как я уже говорил, ложный объект позволяет проверить, как он использовался, как он был вызван и какие аргументы были переданы, а не только возвращаемое значение.
Фиктивные объекты также могут быть и в нетронутом состоянии, т. е. mock объекта еще не был вызван. Это особенно полезно, когда вы хотите сделать несколько вызовов к mock и хотите, чтобы каждый из них работал на новом экземпляре mock.
Побочные эффекты
Давайте вернемся к нашей функции sum. Что делать, если вместо жесткого кодирования возвращаемого значения мы хотим запустить измененную функцию sum? Наша измененная функция mock будет долго выполнять time.sleep(), что нежелательно для нас, и останется только с изначальной функциональностью (суммой), которую мы хотим проверить. Мы можем просто определить side_effect в нашем тесте.
Заключение
В этой статье мы рассмотрели основы mock в Python. Теперь вы знаете, как использовать встроенные возможности Python для замены частей тестируемой системы, чтобы писать более быстрые тесты.
Читайте нас в телеграмме, vk
Перевод статьи Amos Omondi: Getting Started with Mocking in Python