unittest.mock
и pytest-mock
- популярные инструменты.unittest.mock.patch
.Для тестирования сложных взаимодействий между модулями в Python, не вызывая реальные действия в базе данных или внешних сервисах, можно использовать несколько стратегий, комбинируя их в зависимости от ситуации:
unittest.mock
или pytest-mock
):
Это самый распространенный и эффективный способ. Mock-объекты позволяют заменить реальные объекты базы данных, внешних API или других модулей контролируемыми заменителями. Можно настроить их так, чтобы они возвращали определенные значения, возбуждали исключения или записывали информацию о том, какие методы и с какими аргументами были вызваны. pytest-mock
предоставляет удобную фикстуру mocker
для создания и настройки mocks, а unittest.mock
входит в стандартную библиотеку.
Пример (с использованием pytest-mock
):
def test_complex_interaction(mocker):
# Модуль, который мы тестируем
from my_module import my_function
# Создаем mock для базы данных
mock_db = mocker.patch("my_module.db_connection.get_data")
mock_db.return_value = [{"id": 1, "value": "test"}]
# Создаем mock для внешнего API
mock_api = mocker.patch("my_module.external_api.send_request")
mock_api.return_value = {"status": "success"}
# Вызываем функцию, которую тестируем
result = my_function()
# Проверяем, что mock-объекты были вызваны с нужными аргументами
mock_db.assert_called_once_with("some_query")
mock_api.assert_called_once_with(data={"id": 1, "value": "test"})
# Проверяем, что функция вернула ожидаемый результат
assert result == "Expected result"
Stub-объекты проще, чем mock-объекты. Они просто возвращают заранее определенные значения, не проверяя, как они были вызваны. Подходят для ситуаций, когда нужно просто предоставить некоторые данные, а не контролировать поведение зависимостей.
Внедрение зависимостей позволяет легко заменять реальные объекты их mock-версиями в тестах. Вместо жестко закодированных зависимостей в модулях, зависимости передаются как аргументы в конструкторы или функции.
Пример:
def my_function(db_connection, external_api):
data = db_connection.get_data("some_query")
api_result = external_api.send_request(data=data[0])
return api_result
def test_my_function():
# Создаем mock-объекты
mock_db = Mock()
mock_db.get_data.return_value = [{"id": 1, "value": "test"}]
mock_api = Mock()
mock_api.send_request.return_value = {"status": "success"}
# Вызываем функцию, передавая mock-объекты
result = my_function(mock_db, mock_api)
# Проверяем результаты
assert result == {"status": "success"}
mock_db.get_data.assert_called_once_with("some_query")
mock_api.send_request.assert_called_once_with(data={"id": 1, "value": "test"})
Обобщенный термин для mock-объектов, stub-объектов, spies и fakes. Выбор конкретного типа двойника зависит от сложности взаимодействия, которое нужно протестировать.
Подходит для тестирования взаимодействия между микросервисами. Определяется контракт между потребителем (consumer) и поставщиком (provider) API. Потребитель создает тест, который описывает, какие данные он ожидает от поставщика. Затем этот тест используется для проверки поставщика.
Для интеграционных тестов с базами данных можно использовать in-memory базы данных (например, SQLite с :memory:
). Они работают быстро и не требуют настройки реальной базы данных.
Создание упрощенных, но функциональных версий внешних сервисов или компонентов, которые ведут себя подобно оригиналам, но не выполняют реальные действия (например, фейковый SMTP сервер для тестирования отправки email).
Ключевые моменты: