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

Что даёт использование mock в тестах?

1.7 Middle🔥 251 комментариев
#Тестирование

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

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

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

Роль Mock в тестировании

Mock (мок-объект) — это подделка реального объекта, используемая в тестировании. Мок позволяет заменить зависимость реального кода на контролируемую версию, что даёт множество преимуществ при написании юнит-тестов.

Основная идея

# Без mock - тест зависит от реальной БД
def test_get_user_without_mock():
    # Проблемы:
    # - Тест медленный (обращается в реальную БД)
    # - Нужна тестовая БД с данными
    # - Тест нестабилен (может упасть если БД не доступна)
    user = db.get_user(1)  # Реальный запрос!
    assert user.name == "Alice"

# С mock - тест изолирован
from unittest.mock import Mock

def test_get_user_with_mock():
    # Преимущества:
    # - Тест быстрый (не обращается в реальную БД)
    # - Не нужна тестовая БД
    # - Тест стабилен (полный контроль над зависимостями)
    mock_db = Mock()
    mock_db.get_user.return_value = User(id=1, name="Alice")
    
    user = mock_db.get_user(1)
    assert user.name == "Alice"
    assert mock_db.get_user.called  # Проверяем, что функция была вызвана

Основные преимущества Mock

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

class PaymentService:
    def __init__(self, payment_gateway):
        self.gateway = payment_gateway
    
    def charge(self, user_id: int, amount: float):
        # Хотим тестировать логику charging
        # Но НЕ хотим обращаться к реальному payment gateway
        
        result = self.gateway.process_payment(user_id, amount)
        
        if result.success:
            self.log_success(user_id, amount)
        else:
            self.log_failure(user_id, amount)
        
        return result

# Тест с mock
from unittest.mock import Mock

def test_charge_success():
    # Создаём мок gateway вместо реального (Stripe, PayPal)
    mock_gateway = Mock()
    mock_gateway.process_payment.return_value = Mock(success=True)
    
    service = PaymentService(mock_gateway)
    result = service.charge(user_id=1, amount=100.0)
    
    assert result.success
    mock_gateway.process_payment.assert_called_once_with(1, 100.0)

2. Контроль над внешними сервисами

class WeatherService:
    def __init__(self, http_client):
        self.client = http_client
    
    def get_temperature(self, city: str) -> float:
        response = self.client.get(f"https://api.weather.com/{city}")
        if response.status_code == 200:
            return response.json()["temperature"]
        raise WeatherAPIError("Failed to fetch weather")

def test_get_temperature_success():
    # Тестируем успешный сценарий
    mock_client = Mock()
    mock_response = Mock(status_code=200, json=lambda: {"temperature": 25})
    mock_client.get.return_value = mock_response
    
    service = WeatherService(mock_client)
    temp = service.get_temperature("Moscow")
    
    assert temp == 25
    mock_client.get.assert_called_once_with("https://api.weather.com/Moscow")

def test_get_temperature_error():
    # Тестируем обработку ошибки
    mock_client = Mock()
    mock_client.get.return_value = Mock(status_code=500)
    
    service = WeatherService(mock_client)
    
    with pytest.raises(WeatherAPIError):
        service.get_temperature("London")

3. Скорость тестов

# Без mock - тест занимает 2-5 секунд (реальный API запрос)
def test_without_mock():
    response = requests.get("https://api.example.com/data")  # Медленно!
    assert response.status_code == 200

# С mock - тест выполняется за миллисекунды
from unittest.mock import patch

@patch(requests.get)  # Заменяем реальный requests.get на mock
def test_with_mock(mock_get):
    mock_get.return_value.status_code = 200  # Быстро!
    
    response = requests.get("https://api.example.com/data")
    assert response.status_code == 200

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

class DataProcessor:
    def __init__(self, db):
        self.db = db
    
    def process_data(self, data_id: int):
        try:
            data = self.db.get_data(data_id)
            processed = self.process(data)
            self.db.save(processed)
            return processed
        except DatabaseError as e:
            self.log_error(f"DB error: {e}")
            raise

def test_process_data_with_db_error():
    # Тестируем, что система правильно обрабатывает ошибки БД
    mock_db = Mock()
    mock_db.get_data.side_effect = DatabaseError("Connection lost")
    
    processor = DataProcessor(mock_db)
    
    with pytest.raises(DatabaseError):
        processor.process_data(1)
    
    # Проверяем, что ошибка была залогирована
    processor.log_error.assert_called_once()

5. Проверка взаимодействий между объектами

class OrderService:
    def __init__(self, db, email_service, payment_service):
        self.db = db
        self.email = email_service
        self.payment = payment_service
    
    def place_order(self, user_id: int, items: list):
        # Создаём заказ
        order = self.db.create_order(user_id, items)
        
        # Обрабатываем платёж
        payment = self.payment.charge(user_id, order.total)
        
        # Отправляем письмо подтверждения
        self.email.send_confirmation(user_id, order)
        
        return order

def test_place_order():
    mock_db = Mock()
    mock_email = Mock()
    mock_payment = Mock()
    
    mock_db.create_order.return_value = Order(id=1, total=100.0)
    mock_payment.charge.return_value = Payment(success=True)
    
    service = OrderService(mock_db, mock_email, mock_payment)
    order = service.place_order(user_id=1, items=["item1"])
    
    # Проверяем, что все зависимости были вызваны в правильном порядке
    mock_db.create_order.assert_called_once_with(1, ["item1"])
    mock_payment.charge.assert_called_once_with(1, 100.0)
    mock_email.send_confirmation.assert_called_once_with(1, order)

Типы Mock объектов

1. Mock — базовый мок, записывает все вызовы

from unittest.mock import Mock
mock = Mock()
mock.some_method()
assert mock.some_method.called

2. MagicMock — мок с поддержкой магических методов

from unittest.mock import MagicMock
mock_list = MagicMock()
mock_list[0] = "first"  # Поддерживает __getitem__
assert len(mock_list) >= 1

3. patch — заменяет объект в определённой области кода

from unittest.mock import patch

@patch(module.ClassName)  # Заменяет на время теста
def test_something(mock_class):
    mock_class.return_value = Mock(attr="value")

4. PropertyMock — мок для properties

from unittest.mock import PropertyMock

with patch.object(type(obj), property_name, PropertyMock(return_value=42)):
    assert obj.property_name == 42

Реальный пример: Async код с pytest-mock

import pytest
from unittest.mock import AsyncMock

class UserRepository:
    async def get_user(self, user_id: int):
        # Асинхронный запрос в БД
        pass

class UserService:
    def __init__(self, repo):
        self.repo = repo
    
    async def get_user_name(self, user_id: int):
        user = await self.repo.get_user(user_id)
        return user.name

@pytest.mark.asyncio
async def test_get_user_name():
    mock_repo = AsyncMock(spec=UserRepository)
    mock_repo.get_user.return_value = Mock(name="Alice")
    
    service = UserService(mock_repo)
    name = await service.get_user_name(1)
    
    assert name == "Alice"
    mock_repo.get_user.assert_called_once_with(1)

Антипаттерны (чего избегать)

# ПЛОХО - мокируем слишком много
def test_calculation():
    mock_math = Mock()  # Зачем мокировать math?
    # Тест теряет смысл

# ХОРОШО - мокируем только внешние зависимости
def test_calculation():
    mock_api = Mock()  # Мокируем API
    # Реальная логика остаётся реальной

# ПЛОХО - мокируем всю логику
def test_service():
    mock_service = Mock()  # Мок сервиса тестирует самого себя?!
    # Бесполезный тест

# ХОРОШО - тестируем реальную логику с замоканными зависимостями
def test_service():
    mock_db = Mock()  # Только зависимость
    service = RealService(mock_db)  # Реальный сервис

Итог

Mock даёт:

Скорость — тесты выполняются быстро ✅ Надёжность — тесты не зависят от внешних сервисов ✅ Контроль — полный контроль над поведением зависимостей ✅ Изоляция — каждый тест тестирует только одно ✅ Проверка взаимодействий — убеждаемся, что компоненты работают вместе правильно

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