Использование мок-объектов для симуляции отказов и ошибок в распределённых системах – мощный метод для тестирования устойчивости и отказоустойчивости вашего кода. Моки позволяют имитировать непредсказуемое поведение внешних сервисов или компонентов без необходимости запуска реальных аналогов, что экономит ресурсы и упрощает воспроизведение сложных сценариев.
Основные принципы:
Примеры использования в распределённых системах:
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-запросов и ответов.Мокирование отказов и ошибок – важная часть разработки надежных распределенных систем. Оно позволяет выявлять и устранять уязвимости в вашем коде до того, как они возникнут в рабочей среде.