Какие знаешь проблемы использования Mock объектов?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Проблемы использования Mock объектов
Mock объекты — мощный инструмент для unit тестирования, но их неправильное использование может привести к проблемам. Рассмотрю основные ловушки и как их избежать.
1. Тестирование деталей реализации вместо контракта
Самая частая ошибка — мокировать внутренние детали и проверять, как часто вызывалась функция. Это создает хрупкие тесты, которые ломаются при рефакторинге.
# ❌ Плохо: тест зависит от реализации
def test_user_registration(mock_email):
service = UserService()
service.register("user@example.com")
# Этот тест сломается, если добавить кеширование
assert mock_email.send.call_count == 1
# ✅ Хорошо: тест проверяет контракт
def test_user_registered_successfully(mock_email):
service = UserService()
service.register("user@example.com")
# Проверяем, что email был отправлен хотя бы один раз
assert mock_email.send.called
2. Over-mocking (чрезмерное мокирование)
Когда мокируешь слишком много, тест теряет ценность. Ты тестируешь не реальное поведение, а то, как ты себе его представляешь.
# ❌ Плохо: мокируем всё, включая логику
with patch('service.Database'):
with patch('service.Logger'):
with patch('service.Cache'):
result = calculate(10)
assert result == 20 # Не знаешь, работает ли реально
# ✅ Хорошо: используем реальные зависимости где возможно
def test_calculate_with_integration():
# Используем реальную БД (или in-memory), мокируем только внешние API
result = calculate(10)
assert result == 20
3. Тесты не ловят настоящие баги
Если ты мокируешь неправильно, тест пройдет, но код упадет в продакшене.
# ❌ Плохо: mock возвращает строку, а реальный API возвращает объект
with patch('requests.get') as mock_get:
mock_get.return_value = "success" # Строка!
response = fetch_user()
print(response.json()) # AttributeError в продакшене!
# ✅ Хорошо: mock возвращает реалистичный объект
with patch('requests.get') as mock_get:
mock_response = Mock()
mock_response.json.return_value = {"id": 1, "name": "John"}
mock_get.return_value = mock_response
4. Сложность в поддержке
Элабоичные mock setup усложняют тесты и делают их непонятными.
# ❌ Плохо: много строк для подготовки
def test_payment():
with patch('stripe.Charge.create') as mock_charge:
mock_charge.return_value = Mock(
id="ch_123",
amount=1000,
status="succeeded",
created=datetime.now()
)
# И только теперь начинается тест...
result = process_payment(100)
5. Утечки памяти и побочные эффекты
Неправильно созданные mock'и могут оставить глобальное состояние в памяти.
# ❌ Плохо: патч не очищается
patch('module.global_config', {"debug": True})
def test_something():
pass # global_config остается в памяти!
# ✅ Хорошо: используй context manager
def test_something():
with patch('module.global_config', {"debug": True}):
pass # Автоматически восстанавливается
6. Отсутствие side_effect
Мок не симулирует исключения и ошибки, которые могут произойти реально.
# ❌ Плохо: мок всегда успешен
with patch('requests.get') as mock:
mock.return_value = Mock(status_code=200)
# Не тестируем обработку ошибок!
# ✅ Хорошо: проверяем разные сценарии
with patch('requests.get') as mock:
# Успешный случай
mock.return_value = Mock(status_code=200, json=lambda: {"data": 1})
assert fetch_data() == {"data": 1}
# Сценарий ошибки
mock.side_effect = requests.ConnectionError()
with pytest.raises(FetchError):
fetch_data()
Лучшие практики при работе с Mock'ами
1. Используй реальные объекты где возможно
- In-memory БД вместо mock'ов
- Фейковые API вместо patch
- Тестовые контейнеры для интеграционных тестов
2. Mock'ируй границы системы
- Внешние API
- Слой доступа к данным (если используешь абстракцию)
- Медленные операции
3. Проверяй поведение, а не реализацию
- Интересует тебя что произойдет, а не как
- Используй
assert_called_once()только для критичных вызовов
4. Держи mock'и простыми
- Не делай их слишком сложными
- Лучше создать несколько тестов, чем один большой
5. Документируй intent
- Почему ты мокируешь этот объект
- Какое реальное поведение он симулирует
Правило 80/20
Мок объекты должны составлять не более 20% твоих тестов. 80% — это интеграционные тесты с реальными зависимостями. Это обеспечивает уверенность, что код действительно работает.