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

Каки плюсы и минусы Mock в тестировании

2.3 Middle🔥 161 комментариев
#Тестирование

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

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

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

Плюсы и минусы Mock в тестировании

Mock (подделка) — это объект, который имитирует поведение реального объекта, но контролируется в тестах. Это мощный инструмент, но требует аккуратного использования.

Плюсы Mock

1. Изоляция компонентов

Mock позволяет тестировать отдельный компонент в изоляции от его зависимостей:

from unittest.mock import Mock

# Тестируем UserService без реальной базы данных
class UserService:
    def __init__(self, db):
        self.db = db
    
    def get_user(self, user_id):
        return self.db.query(user_id)

# Mock базы данных
mock_db = Mock()
mock_db.query.return_value = {"id": 1, "name": "John"}

service = UserService(mock_db)
user = service.get_user(1)

assert user["name"] == "John"
mock_db.query.assert_called_once_with(1)

2. Тестирование сложных сценариев

Legко создать сценарии, которые сложно воспроизвести в реальности:

from unittest.mock import Mock
import requests

# Тестируем обработку HTTP ошибок
mock_response = Mock()
mock_response.status_code = 500
mock_response.text = "Internal Server Error"

# Можем проверить, как код обрабатывает ошибки
with patch('requests.get', return_value=mock_response):
    # Код обработает ошибку
    pass

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

Моки исключают медленные операции (DB, API, файловая система):

# Без mock — тест длится несколько секунд
# def test_user_registration():  # Создаёт БД, отправляет email
#     result = register_user("john@example.com")

# С mock — микросекунды
def test_user_registration():
    mock_email = Mock()
    mock_db = Mock()
    
    register_user_with_mocks("john@example.com", mock_db, mock_email)
    
    mock_db.save.assert_called_once()
    mock_email.send.assert_called_once()

4. Проверка вызовов

Можно проверить, были ли вызваны методы с правильными аргументами:

from unittest.mock import Mock, call

mock_api = Mock()

class PaymentProcessor:
    def process(self, api, items):
        for item in items:
            api.charge(item.price)

processor = PaymentProcessor()
mock_api = Mock()
items = [Mock(price=100), Mock(price=200)]

processor.process(mock_api, items)

# Проверяем вызовы
assert mock_api.charge.call_count == 2
mock_api.charge.assert_any_call(100)
mock_api.charge.assert_any_call(200)

5. Тестирование обработки исключений

Легко инициировать исключения для проверки обработки:

from unittest.mock import Mock

def test_error_handling():
    mock_db = Mock()
    mock_db.query.side_effect = ConnectionError("Database offline")
    
    service = UserService(mock_db)
    
    with pytest.raises(ConnectionError):
        service.get_user(1)

Минусы Mock

1. Ложное чувство безопасности

Тесты проходят с mock, но код падает в production с реальными компонентами:

# Тест проходит
def test_api_call():
    mock_api = Mock()
    mock_api.get.return_value = {"status": "ok"}
    
    result = call_api(mock_api)
    assert result["status"] == "ok"

# Но реальная API может возвращать совсем другое
# {"code": 200, "data": {...}} вместо {"status": "ok"}

2. Тесты привязаны к реализации

При изменении реализации тесты ломаются, хотя функциональность верна:

# Плохо — тест знает о внутренней реализации
def test_user_creation():
    mock_db = Mock()
    
    create_user("john", mock_db)  # Тест знает о порядке аргументов
    mock_db.insert.assert_called_once()  # Тест знает о методе insert

# Хорошо — тест проверяет результат
def test_user_creation():
    user = create_user("john")
    assert user.name == "john"

3. Сложность при глубоком mocking

Когда нужно mock много зависимостей, тесты становятся сложными:

# Адский код с множеством mock'ов
def test_payment_flow():
    mock_db = Mock()
    mock_api = Mock()
    mock_email = Mock()
    mock_sms = Mock()
    mock_cache = Mock()
    
    # Настройка всех этих mock'ов занимает 50 строк кода...
    # Если нужно так много mock'ов — это признак плохого дизайна

4. Отсутствие интеграционного тестирования

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

# Тест с mock'ом пройдёт
def test_database_integration():
    mock_db = Mock()
    user = service.create_user("john", mock_db)
    assert user.id == 1

# Но реальная БД может:
# - Иметь другую схему
# - Быть недоступной
# - Выбросить исключение при создании

5. Сложность поддержки

Когда спецификация меняется, приходится обновлять много mock'ов:

# Если API добавит новый параметр
def call_api(api, user_id, timeout=30):  # Добавили timeout
    return api.get(user_id, timeout=timeout)

# Придётся обновлять все mock'ы в тестах

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

1. Используйте mock для внешних зависимостей

# Хорошо — mock для внешних сервисов
mock_payment_api = Mock()
mock_email_service = Mock()

# Плохо — mock для вашего собственного кода
# mock_user_model = Mock()  # ❌

2. Комбинируйте unit и integration тесты

# Unit тесты — с mock'ами (быстро)
def test_user_service_with_mock():
    mock_db = Mock()
    service = UserService(mock_db)
    # ...

# Integration тесты — с реальной БД (медленнее, но важно)
def test_user_service_integration():
    service = UserService(real_db)
    # ...

3. Проверяйте поведение, не деталь реализации

# Плохо — привязано к реализации
def test_calculation():
    mock_logger = Mock()
    result = calculate(10, 20, mock_logger)
    mock_logger.debug.assert_called()  # Не важно!

# Хорошо — проверяем результат
def test_calculation():
    result = calculate(10, 20)
    assert result == 30

Вывод

Mock — мощный инструмент, но используйте его умело:

  • Используйте для изоляции от внешних зависимостей (API, БД, файлы)
  • Избегайте для тестирования собственного кода
  • Дополняйте mock-тесты интеграционными тестами
  • Помните про ложное чувство безопасности
  • Тестируйте реальное поведение, не детали реализации
Каки плюсы и минусы Mock в тестировании | PrepBro