Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Мокирование: зачем и когда
why_mocking

Мокирование: зачем и когда

Что такое моки, когда использовать, когда избегать. Test doubles: моки vs стабы vs фейки

Вы написали 50 тестов для банковского приложения. Тесты работают 5 минут. Почему? Каждый тест обращается к реальной БД и внешнему API валют. Как ускорить?

Цель: Понять зачем нужно мокирование и когда его использовать.

Вы точно готовы?

Убедитесь, что прошли pytest-basics:

# Вы должны уметь писать тесты с фикстурами @pytest.fixture def account(): return BankAccount(100) def test_deposit(account): account.deposit(50) assert account.balance == 150

Если фикстуры или parametrize непонятны — вернитесь к Pytest с нуля.

Проблема: тесты с реальными зависимостями

Медленные тесты

# src/weather_service.py import requests class WeatherService: def get_temperature(self, city): """Получает температуру из внешнего API""" response = requests.get( f"https://api.weather.com/current?city={city}" ) data = response.json() return data["temperature"] # tests/test_weather.py def test_get_temperature(): """Тест получения температуры""" service = WeatherService() temperature = service.get_temperature("Moscow") assert temperature > -50 # Реальный HTTP-запрос!

Проблемы:

  • ❌ Медленно: HTTP-запрос занимает 500ms
  • ❌ Ненадёжно: Если API недоступен — тест падает
  • ❌ Не изолировано: Тест зависит от интернета
  • ❌ Не детерминировано: Погода меняется каждый день

Запускаем 50 тестов:

pytest tests/ # Время: 25 секунд (50 * 500ms) # FAILED: ConnectionError (API недоступен)

❌ Тесты не должны зависеть от внешних систем!

Непредсказуемые тесты

from datetime import datetime def test_user_birthday(): """Проверяем что пользователь получает скидку в день рождения""" user = User("Alice", birthday="2000-01-15") # ❌ Тест работает только 15 января! if datetime.now().date() == user.birthday: assert user.get_discount() == 0.2 else: assert user.get_discount() == 0.0

Проблема: Тест работает по-разному в зависимости от даты запуска!

Побочные эффекты

def test_save_report(): """Тест сохранения отчёта""" report = Report(data=[1, 2, 3]) # ❌ Записываем реальный файл на диск! report.save_to_file("report.csv") # Файл создан — нужно удалить вручную assert os.path.exists("report.csv")

Проблемы:

  • ❌ Создаются реальные файлы
  • ❌ Нужен cleanup
  • ❌ Могут быть конфликты (файл уже существует)

Решение: мокирование (test doubles)

Что такое мокирование

Мокирование — замена реальных зависимостей на "поддельные" объекты для тестирования.

Пример:

# Было: реальный HTTP-запрос response = requests.get("https://api.weather.com/...") # Стало: мок возвращает фиксированные данные mock_response = Mock() mock_response.json.return_value = {"temperature": 15}

✅ Тест работает мгновенно и предсказуемо!

Test doubles: типы поддельных объектов

# 1. STUB — возвращает фиксированные данные class StubWeatherAPI: def get_temperature(self, city): return 15 # Всегда 15°C # 2. MOCK — проверяет вызовы + возвращает данные class MockWeatherAPI: def __init__(self): self.calls = [] def get_temperature(self, city): self.calls.append(city) # Запоминаем вызов return 15 # 3. FAKE — рабочая имплементация (но упрощённая) class FakeDatabase: def __init__(self): self.data = {} # Не реальная БД, а dict def save(self, key, value): self.data[key] = value def get(self, key): return self.data.get(key) # 4. SPY — обёртка над реальным объектом (записывает вызовы) class SpyWeatherAPI: def __init__(self, real_api): self.real_api = real_api self.calls = [] def get_temperature(self, city): self.calls.append(city) return self.real_api.get_temperature(city) # Реальный вызов!

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

ТипНазначениеПример
StubПростые данные без проверки вызововAPI возвращает фиксированный JSON
MockПроверка взаимодействия (сколько раз вызвали)Убедиться что email отправлен 1 раз
FakeРабочая in-memory имплементацияIn-memory БД для быстрых тестов
SpyПроверка реального объекта (редко)Логирование реальных вызовов

В pytest обычно используют Mock и Stub.

Пример: без мока VS с моком

Без мока (медленно):

def test_weather_forecast(): """Тест прогноза погоды (медленный)""" service = WeatherService() # Реальный HTTP-запрос (500ms) forecast = service.get_forecast("Moscow", days=7) assert len(forecast) == 7 # Тест может упасть если API недоступен!

С моком (быстро):

from unittest.mock import Mock def test_weather_forecast_mocked(): """Тест прогноза погоды (быстрый)""" # Создаём мок API mock_api = Mock() mock_api.get_forecast.return_value = [ {"day": 1, "temp": 15}, {"day": 2, "temp": 16}, # ... 7 дней ] service = WeatherService(api=mock_api) # Мгновенный вызов (< 1ms) forecast = service.get_forecast("Moscow", days=7) assert len(forecast) == 7 # Проверяем что API вызван правильно mock_api.get_forecast.assert_called_once_with("Moscow", days=7)

✅ Тест работает мгновенно, не зависит от сети!

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

✅ Используйте моки для:

1. Внешние системы (HTTP, API)

# Мокируем requests @patch("requests.get") def test_fetch_user(mock_get): mock_get.return_value.json.return_value = {"name": "Alice"} # Тест без реального HTTP

2. Файловый ввод-вывод

# Мокируем open() @patch("builtins.open", mock_open(read_data="test data")) def test_read_file(): # Тест без реальных файлов

3. Время и даты

# Мокируем datetime.now() @freeze_time("2025-01-15") def test_birthday_discount(): # Тест всегда работает как будто сегодня 15 января

4. База данных

# Используем fake in-memory БД @pytest.fixture def fake_db(): return FakeDatabase() def test_save_user(fake_db): fake_db.save("user_1", {"name": "Alice"}) # Быстрый тест без реальной БД

5. Медленные операции

# Мокируем медленную функцию @patch("image_processor.resize_image") def test_upload_avatar(mock_resize): mock_resize.return_value = "resized.jpg" # Тест без реальной обработки изображений

❌ НЕ мокируйте:

1. Чистую бизнес-логику

# ❌ ПЛОХО — мокируем метод который тестируем def test_calculate_discount(): order = Order(total=100) order.calculate_discount = Mock(return_value=90) # Бессмысленно! result = order.calculate_discount() assert result == 90 # Тестируем мок, не код!

2. Код внутри тестируемого модуля

# ✅ ХОРОШО — тестируем реальную логику def test_calculate_total(): cart = ShoppingCart() cart.add_item(price=100, quantity=2) # Реальное вычисление total = cart.calculate_total() assert total == 200

3. Простые объекты (User, BankAccount)

# ❌ ПЛОХО def test_user(): user = Mock(name="Alice", age=25) # Зачем? # ✅ ХОРОШО def test_user(): user = User("Alice", 25) # Реальный объект быстрый

Практические рекомендации

Правило 1: Мокируйте границы системы

Ваш код  →  [Мок HTTP]  →  Внешний API
Ваш код  →  [Мок DB]    →  PostgreSQL
Ваш код  →  [Мок File]  →  Файловая система

✅ Мокируйте всё что выходит за пределы вашего процесса.

Правило 2: Не мокируйте то чем вы владеете

# ❌ ПЛОХО — мокируем свой код @patch("src.calculator.add") def test_calculator(mock_add): mock_add.return_value = 5 result = Calculator().compute(2, 3) # Тестируем мок, не Calculator! # ✅ ХОРОШО — тестируем реальный код def test_calculator(): result = Calculator().compute(2, 3) assert result == 5

Правило 3: Integration vs Unit тесты

Unit-тесты: Тестируют одну функцию/класс изолированно

# Мокируем всё кроме тестируемого класса def test_order_service_unit(mock_db, mock_email): service = OrderService(db=mock_db, email=mock_email) service.create_order(user_id=1, items=[...]) mock_db.save.assert_called_once() mock_email.send.assert_called_once()

Integration-тесты: Тестируют взаимодействие нескольких компонентов

# Минимум моков (только внешние системы) def test_order_service_integration(fake_db): email_service = RealEmailService() service = OrderService(db=fake_db, email=email_service) order = service.create_order(user_id=1, items=[...]) # Проверяем реальное взаимодействие assert fake_db.get_order(order.id) is not None

Баланс: 70% unit (с моками) + 30% integration (минимум моков)

Что вы изучили

  • Проблемы без моков — медленные, ненадёжные, непредсказуемые тесты
  • Test doubles — mock, stub, fake, spy
  • Когда мокировать — HTTP, файлы, время, БД, медленные операции
  • Когда НЕ мокировать — чистая логика, простые объекты, свой код
  • Практические guidelines — мокируйте границы системы

Проверьте свои знания

Вопросы ещё не добавлены

Вопросы для этой подтемы ещё не добавлены.

Далее: unittest.mock: Mock, patch, MagicMock