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

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

@potapov_me

Платформа

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

Контент

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

Компания

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

Аккаунт

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

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

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Integration vs Unit: границы тестирования
integration_vs_unit

Integration vs Unit: границы тестирования

Что такое integration test? Когда моки вредят? Пирамида тестирования, test doubles, социальные vs одиночные тесты. Фундамент для понимания курса

Этот урок — фундамент курса. Мы разберём границу между unit и integration тестами и поймём, когда моки помогают, а когда вредят.

Главная цель: понять, что вы тестируете integration tests, а не unit tests с моками.

Что вы узнали в базовом курсе

В курсе "Pytest: С нуля до уверенности" вы научились:

✅ Писать unit tests ✅ Мокировать зависимости (HTTP API, файлы, время) ✅ Использовать фикстуры ✅ Проверять coverage

Это unit testing — тестирование изолированных компонентов.

Проблема unit tests: ложная уверенность

Пример: сервис отправки email

# email_service.py class EmailService: def __init__(self, smtp_client): self.smtp = smtp_client def send_welcome(self, user_email: str): subject = "Welcome!" body = f"Hello {user_email}" self.smtp.send(to=user_email, subject=subject, body=body)

Unit тест (с моком):

# tests/test_email_service.py from unittest.mock import Mock def test_send_welcome(): """Unit test — изолированный""" mock_smtp = Mock() service = EmailService(mock_smtp) service.send_welcome("user@test.com") # Проверяем что метод вызвался mock_smtp.send.assert_called_once_with( to="user@test.com", subject="Welcome!", body="Hello user@test.com" )

✅ Тест проходит!

Но в production:

# Реальный SMTP клиент class SMTPClient: def send(self, recipient, title, message): # ❌ Другие параметры! # Отправка email pass

❌ Production падает! API не совпадает: to vs recipient, subject vs title, body vs message.

Проблема: Unit тест проверил что метод вызвался, но не проверил что интеграция работает.

Что такое integration test?

Integration test — тест, который проверяет взаимодействие между компонентами.

Integration тест (без мока):

# tests/test_email_integration.py import pytest from email_service import EmailService from smtp_client import SMTPClient # Реальный клиент! @pytest.fixture def smtp_client(): """Реальный SMTP клиент (тестовый режим)""" return SMTPClient(host="localhost", port=1025) # Fake SMTP server def test_send_welcome_integration(smtp_client): """Integration test — реальная интеграция""" service = EmailService(smtp_client) service.send_welcome("user@test.com") # Проверяем что email РЕАЛЬНО отправился # (можем проверить через fake SMTP server)

Если API не совпадает — тест упадёт!

Пирамида тестирования

         /\
        /  \        E2E Tests (UI, API, slow)
       /    \       ← Мало тестов, медленные
      /------\
     /        \     Integration Tests
    /          \    ← Средне тестов, средняя скорость
   /------------\
  /              \  Unit Tests
 /________________\ ← Много тестов, быстрые

Правило:

  • 70% unit tests (быстрые, изолированные)
  • 20% integration tests (средние, реальные зависимости)
  • 10% E2E tests (медленные, полный сценарий)

Почему не 100% unit tests? Моки дают ложную уверенность — тесты зелёные, production падает.

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

✅ Мокируйте: внешние сервисы

Плохо контролируемые:

  • HTTP API третьих сторон (Stripe, AWS)
  • Email-серверы
  • SMS-гейтвеи
  • External webhooks

Почему мокировать:

  • ❌ Медленно (сетевые запросы)
  • ❌ Дорого (платные API)
  • ❌ Нестабильно (сервис может упасть)
  • ❌ Сложно настроить (нужны credentials)

Пример:

# ✅ МОКИРУЕМ Stripe API from unittest.mock import patch @patch('stripe.Charge.create') def test_payment(mock_stripe): mock_stripe.return_value = {"id": "ch_123", "status": "succeeded"} result = process_payment(100, "usd") assert result["status"] == "succeeded"

✅ Используйте реальные: внутренние компоненты

Под вашим контролем:

  • PostgreSQL, Redis (в Docker!)
  • Ваш REST API
  • Ваши классы и модули
  • Файловая система (в /tmp)

Почему реальные:

  • ✅ Проверяют реальную интеграцию
  • ✅ Ловят ошибки совместимости
  • ✅ Тестируют SQL-запросы, constraints
  • ✅ Можно контролировать (Docker, testcontainers)

Пример:

# ✅ ИСПОЛЬЗУЕМ реальный PostgreSQL import psycopg2 def test_create_user(db_connection): """Реальная БД в тесте""" cur = db_connection.cursor() cur.execute("INSERT INTO users (email) VALUES (%s)", ("user@test.com",)) cur.execute("SELECT email FROM users WHERE email=%s", ("user@test.com",)) result = cur.fetchone() assert result[0] == "user@test.com"

Test Doubles: типы заглушек

Test Double — общий термин для заглушек в тестах.

1. Dummy

Объект который передаётся но не используется.

def test_user_registration(): logger = None # Dummy — не используется в тесте service = UserService(logger) service.register("user@test.com")

2. Stub

Возвращает заранее заданные данные.

class StubUserRepository: def get_user(self, user_id): return {"id": 1, "email": "stub@test.com"} # Всегда одно и то же def test_get_user(): repo = StubUserRepository() user = repo.get_user(123) assert user["email"] == "stub@test.com"

3. Spy

Записывает как его вызывали.

class SpyEmailService: def __init__(self): self.calls = [] def send(self, to, subject, body): self.calls.append({"to": to, "subject": subject, "body": body}) def test_send_email(): spy = SpyEmailService() spy.send("user@test.com", "Hi", "Hello") assert len(spy.calls) == 1 assert spy.calls[0]["to"] == "user@test.com"

4. Mock

Проверяет что методы вызвались правильно.

from unittest.mock import Mock def test_with_mock(): mock_smtp = Mock() service = EmailService(mock_smtp) service.send_welcome("user@test.com") # Mock проверяет вызов mock_smtp.send.assert_called_once_with( to="user@test.com", subject="Welcome!", body="Hello user@test.com" )

5. Fake

Работающая упрощённая реализация.

class FakeDatabase: """Работающая БД в памяти""" def __init__(self): self.users = {} def insert(self, user_id, email): self.users[user_id] = email def get(self, user_id): return self.users.get(user_id) def test_with_fake(): db = FakeDatabase() db.insert(1, "user@test.com") assert db.get(1) == "user@test.com"

Integration test чаще использует Fake или реальные зависимости.

Социальные vs Одиночные тесты

Одиночные тесты (Solitary Tests)

Изолируют тестируемый класс от ВСЕХ зависимостей.

def test_solitary(): """Всё замоканно""" mock_db = Mock() mock_cache = Mock() mock_logger = Mock() service = UserService(mock_db, mock_cache, mock_logger) service.create_user("user@test.com") # Проверяем только тестируемый класс

Плюсы:

  • ✅ Быстрые
  • ✅ Изолированные (один класс)

Минусы:

  • ❌ Не тестируют интеграцию
  • ❌ Много boilerplate (моки)

Социальные тесты (Sociable Tests)

Используют реальные зависимости где возможно.

def test_sociable(db_connection): """Реальная БД, реальный кеш""" cache = RealCache() logger = RealLogger() service = UserService(db_connection, cache, logger) service.create_user("user@test.com") # Проверяем результат в реальной БД user = db_connection.execute("SELECT * FROM users WHERE email=?", ("user@test.com",)) assert user is not None

Плюсы:

  • ✅ Тестируют реальную интеграцию
  • ✅ Ловят ошибки API

Минусы:

  • ❌ Медленнее
  • ❌ Требуют setup (БД, Docker)

Integration tests = sociable tests.

Практика: когда моки вредят

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

Unit тест (с моком):

def test_save_user_unit(): mock_db = Mock() repo = UserRepository(mock_db) repo.save({"id": 1, "email": "user@test.com"}) # Проверяем что insert вызвался mock_db.insert.assert_called_once() # ✅ Проходит

Integration тест (реальная БД):

def test_save_user_integration(db_connection): repo = UserRepository(db_connection) repo.save({"id": 1, "email": "user@test.com"}) # Проверяем что данные РЕАЛЬНО сохранились cur = db_connection.cursor() cur.execute("SELECT email FROM users WHERE id=1") result = cur.fetchone() assert result[0] == "user@test.com" # ✅ Проверили реальность

Что может пойти не так с моком:

  • ❌ SQL-запрос неправильный (мок не проверит)
  • ❌ Constraint violation (мок не проверит)
  • ❌ Тип данных не совпадает (мок не проверит)

Integration тест ловит всё это.

Правило: мокируйте края, тестируйте ядро

┌─────────────────────────────────────┐
│  External Services (MOCK)           │  ← Мокируем
│  - Stripe API                       │
│  - Email SMTP                       │
│  - AWS S3                           │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│  Your Application (REAL)            │  ← Интеграция
│  - Business Logic                   │
│  - Database                         │
│  - Internal APIs                    │
└─────────────────────────────────────┘

Мокируйте только то, что не контролируете.

Что вы узнали

✅ Integration test проверяет взаимодействие компонентов ✅ Моки дают ложную уверенность если переиспользуются ✅ Пирамида тестирования: 70% unit, 20% integration, 10% E2E ✅ Test Doubles: Dummy, Stub, Spy, Mock, Fake ✅ Социальные тесты используют реальные зависимости ✅ Правило: мокируйте края (внешние API), тестируйте ядро (БД, ваш код)

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

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

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

Далее: PostgreSQL + Alembic: миграции в тестах