Пример (pytest):
def my_function(api):
# Использует api
return api.get_data()
def test_my_function(mocker):
mock_api = mocker.MagicMock()
mock_api.get_data.return_value = "test_data"
result = my_function(mock_api)
assert result == "test_data"
mock_api.get_data.assert_called_once()
Цель: Изолировать функцию от внешних зависимостей, обеспечить предсказуемый результат теста.
При тестировании функций, взаимодействующих с внешними ресурсами (базы данных, API, файловая система), важно изолировать логику самой функции от нестабильности и непредсказуемости этих ресурсов. Мок-объекты (mocks) позволяют заменить реальные внешние зависимости их контролируемыми заменителями, что делает тесты более надежными, быстрыми и предсказуемыми.
Основные принципы использования мок-объектов:
Пример:
Предположим, у нас есть функция, которая получает данные пользователя из API:
import requests
def get_user_data(user_id):
"""Получает данные пользователя из API."""
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status() # Вызывает исключение для кодов ошибок
return response.json()
Чтобы протестировать эту функцию, используя мок-объекты, можно воспользоваться библиотекой unittest.mock
(встроена в Python) или pytest-mock
(более удобная для использования с pytest):
Пример с unittest.mock
:
import unittest
from unittest.mock import patch, MagicMock
import requests
# Функция, которую мы тестируем
def get_user_data(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status()
return response.json()
class TestGetUserData(unittest.TestCase):
@patch('requests.get')
def test_get_user_data_success(self, mock_get):
# Настраиваем мок-объект
mock_response = MagicMock()
mock_response.json.return_value = {"id": 1, "name": "John Doe"}
mock_response.raise_for_status.return_value = None # Имитируем успешный ответ
mock_get.return_value = mock_response
# Вызываем функцию и проверяем результат
user_data = get_user_data(1)
self.assertEqual(user_data, {"id": 1, "name": "John Doe"})
# Проверяем, что requests.get был вызван с правильным URL
mock_get.assert_called_once_with("https://api.example.com/users/1")
@patch('requests.get')
def test_get_user_data_error(self, mock_get):
# Настраиваем мок-объект для имитации ошибки
mock_response = MagicMock()
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("Ошибка API")
mock_get.return_value = mock_response
# Проверяем, что функция вызывает исключение
with self.assertRaises(requests.exceptions.HTTPError):
get_user_data(1)
# Проверяем, что requests.get был вызван с правильным URL
mock_get.assert_called_once_with("https://api.example.com/users/1")
if __name__ == '__main__':
unittest.main()
Пояснения:
@patch('requests.get')
: Этот декоратор заменяет реальную функцию requests.get
мок-объектом. Мок-объект передается в качестве аргумента в тестовую функцию (mock_get
).MagicMock
: Универсальный мок-объект, который позволяет имитировать любые методы и атрибуты.mock_response.json.return_value = {"id": 1, "name": "John Doe"}
: Указывает, что при вызове mock_response.json()
мок-объект должен вернуть словарь с данными пользователя.mock_response.raise_for_status.return_value = None
: Указывает, что при вызове mock_response.raise_for_status()
, который вызывается для проверки статуса ответа API, мок должен вернуть None (т.е. имитирует успешный ответ 200 OK).mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("Ошибка API")
: Указывает, что при вызове mock_response.raise_for_status()
, мок-объект должен вызвать исключение requests.exceptions.HTTPError
, имитируя ошибку от API.mock_get.assert_called_once_with("https://api.example.com/users/1")
: Проверяет, что функция requests.get
была вызвана ровно один раз и с ожидаемым URL.Преимущества использования мок-объектов:
Вывод:
Использование мок-объектов - важный навык для Python-разработчика. Это позволяет писать надежные, предсказуемые и быстрые тесты для функций, взаимодействующих с внешними ресурсами.