Использование мок-объектов для симуляции отказов и ошибок в распределённых системах – мощный метод для тестирования устойчивости и отказоустойчивости вашего кода. Моки позволяют имитировать непредсказуемое поведение внешних сервисов или компонентов без необходимости запуска реальных аналогов, что экономит ресурсы и упрощает воспроизведение сложных сценариев.
Основные принципы:
Примеры использования в распределённых системах:
session.commit()
так, чтобы он вызывал исключение sqlalchemy.exc.OperationalError
.requests.get()
так, чтобы она возвращала объект Response
с кодом статуса 503.publish()
у объекта брокера сообщений так, чтобы он вызывал исключение pika.exceptions.AMQPConnectionError
.get()
у объекта Redis так, чтобы он возвращал None
.acquire()
у объекта блокировки так, чтобы он всегда возвращал False
.Пример кода (использование unittest.mock
):
import unittest
from unittest.mock import patch
import requests
def get_data_from_api(url):
response = requests.get(url)
response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
return response.json()
class TestGetDataFromApi(unittest.TestCase):
@patch('requests.get')
def test_api_unavailable(self, mock_get):
mock_get.side_effect = requests.exceptions.ConnectionError("Mocked connection error")
with self.assertRaises(requests.exceptions.ConnectionError):
get_data_from_api("https://example.com/api")
@patch('requests.get')
def test_api_returns_error(self, mock_get):
mock_response = unittest.mock.Mock()
mock_response.status_code = 500
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("Mocked 500 error") # Make it raise an exception on raise_for_status
mock_get.return_value = mock_response
with self.assertRaises(requests.exceptions.HTTPError):
get_data_from_api("https://example.com/api")
@patch('requests.get')
def test_api_returns_valid_data(self, mock_get):
mock_response = unittest.mock.Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"key": "value"}
mock_get.return_value = mock_response
data = get_data_from_api("https://example.com/api")
self.assertEqual(data, {"key": "value"})
if __name__ == '__main__':
unittest.main()
Ключевые моменты в примере:
@patch('requests.get')
: Используем декоратор patch
из модуля unittest.mock
для замены функции requests.get
мок-объектом.mock_get.side_effect = ...
: Устанавливаем side_effect
мок-объекта, чтобы он вызывал исключение requests.exceptions.ConnectionError
. Это имитирует ситуацию, когда API недоступен.mock_response.status_code = 500
и mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError(...)
: Имитируем ответ сервера с кодом 500 и гарантируем вызов исключения при попытке проверки статуса (raise_for_status
).mock_response.json.return_value = {"key": "value"}
: Задаем возвращаемое значение метода json()
мок-объекта, чтобы имитировать успешный ответ API с данными.Рекомендации:
with patch(...)
) для мокирования.unittest.mock
предоставляет методы для этого (например, mock_object.assert_called_with(...)
).responses
. Они предоставляют более удобный и декларативный способ мокирования HTTP-запросов и ответов.Мокирование отказов и ошибок – важная часть разработки надежных распределенных систем. Оно позволяет выявлять и устранять уязвимости в вашем коде до того, как они возникнут в рабочей среде.