← Назад к вопросам

Для чего нужен МОК?

1.0 Junior🔥 251 комментариев
#Python Core#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего нужен MOC (Mock Object)?

Мок-объекты — это один из ключевых инструментов в тестировании и разработке. Они имитируют поведение реальных объектов, позволяя тестировать код в изоляции.

Основное назначение

Моки решают главную проблему unit-тестов: зависимости. Когда функция зависит от внешних сервисов, баз данных или API, прямое тестирование усложняется. Мок позволяет заменить эту зависимость на контролируемый подставной объект.

Основные причины использования мокирования

1. Изоляция кода от внешних зависимостей

Представим функцию, которая делает запрос к API. Без мока тест зависит от доступности этого API.

import requests

def get_user_info(user_id):
    response = requests.get(f'https://api.example.com/users/{user_id}')
    return response.json()

Tестировать эту функцию напрямую невозможно, если API недоступна. С мокированием:

from unittest.mock import Mock, patch
import pytest

def test_get_user_info():
    with patch('requests.get') as mock_get:
        mock_get.return_value = Mock(json=lambda: {"id": 1, "name": "John"})
        result = get_user_info(1)
        assert result['name'] == 'John'
        mock_get.assert_called_once_with('https://api.example.com/users/1')

2. Тестирование обработки ошибок

Моки позволяют эмулировать различные сценарии: сетевые ошибки, таймауты, различные HTTP коды.

def test_get_user_info_error():
    with patch('requests.get') as mock_get:
        mock_get.side_effect = requests.ConnectionError('Network error')
        
        with pytest.raises(requests.ConnectionError):
            get_user_info(1)

3. Ускорение тестов

Моки исключают медленные операции (БД, HTTP запросы, файловая система). Тесты выполняются в миллисекундах вместо секунд.

4. Проверка взаимодействия между компонентами (behaviour verification)

Моки записывают, как с ними взаимодействуют, позволяя проверить корректность вызовов.

def test_order_processing():
    mock_payment = Mock()
    mock_email = Mock()
    
    process_order(order_id=123, payment=mock_payment, email=mock_email)
    
    mock_payment.charge.assert_called_once_with(amount=100)
    mock_email.send_confirmation.assert_called_once()

Основные типы mocks

Mock — самый общий случай, может имитировать любой объект и отслеживать взаимодействия.

from unittest.mock import Mock

mock_db = Mock()
mock_db.get_user.return_value = {"id": 1}
mock_db.get_user(1)
mock_db.get_user.assert_called_with(1)

MagicMock — расширенный Mock с поддержкой магических методов (__str__, __len__ и т.д.).

from unittest.mock import MagicMock

mock_list = MagicMock()
mock_list.__len__.return_value = 5
print(len(mock_list))  # 5

patch — временно заменяет объект в коде.

with patch('module.ClassName') as mock_class:
    mock_class.return_value = Mock(method=Mock(return_value='result'))

Spy (использование wraps) — отслеживает вызовы реального объекта, не меняя его поведение.

original = Mock(return_value=42)
spy = Mock(wraps=original)
spy()
spy.assert_called_once()
assert original.called

Практический пример: тестирование сервиса заказов

from unittest.mock import Mock, patch, call
import pytest

class OrderService:
    def __init__(self, payment_gateway, email_service):
        self.payment_gateway = payment_gateway
        self.email_service = email_service
    
    def create_order(self, user_id, items):
        total = sum(item['price'] for item in items)
        
        try:
            self.payment_gateway.charge(user_id, total)
        except Exception as e:
            raise ValueError(f"Payment failed: {e}")
        
        self.email_service.send_order_confirmation(user_id)
        return {"user_id": user_id, "items": items, "total": total}

def test_create_order_success():
    mock_payment = Mock()
    mock_email = Mock()
    service = OrderService(mock_payment, mock_email)
    
    items = [{"name": "item1", "price": 100}]
    result = service.create_order(user_id=1, items=items)
    
    assert result['total'] == 100
    mock_payment.charge.assert_called_once_with(1, 100)
    mock_email.send_order_confirmation.assert_called_once_with(1)

def test_create_order_payment_fails():
    mock_payment = Mock(side_effect=Exception("Card declined"))
    mock_email = Mock()
    service = OrderService(mock_payment, mock_email)
    
    items = [{"name": "item1", "price": 100}]
    
    with pytest.raises(ValueError, match="Payment failed"):
        service.create_order(user_id=1, items=items)
    
    mock_email.send_order_confirmation.assert_not_called()

Когда использовать моки

  • Внешние API — REST, SOAP сервисы
  • Базы данных — для unit-тестов (для интеграционных используй real БД)
  • Файловая система — создание временных файлов в тестах
  • Дорогие операции — криптография, машинное обучение
  • Случайные значения — время, генераторы случайных чисел
  • Сторонние библиотеки — когда не контролируешь код

Когда НЕ использовать моки

  • Бизнес-логика — тестируй реальный код
  • Взаимодействие между компонентами — лучше интеграционный тест
  • Всё подряд — приводит к хрупким тестам, которые не ловят реальные ошибки

Лучшие практики

  1. Используй моки для изоляции, а не для скрытия проблем
  2. Проверяй поведение, а не реализацию
  3. Не создавай слишком сложные моки — если сложно мокировать, дизайн плохой
  4. Тестируй взаимодействия, но не переусложняй
  5. Комбинируй unit (с моками) и интеграционные (без мокирования) тесты

Моки — это не способ избежать написания хорошего кода, а инструмент для его проверки.