Сложности тестирования микросервисов

Архитектуры микросервисов разбивают монолитное приложение на десятки или сотни независимо развёртываемых сервисов. Каждый сервис владеет своими данными, общается по сети и может быть написан на другом языке. Это даёт гибкость развёртывания, но драматически увеличивает сложность тестирования.

В монолите вы тестируете одно приложение. В микросервисах вы тестируете множество сервисов и взаимодействия между ними. Баг может не существовать ни в одном отдельном сервисе — он может проявиться только когда Сервис A отправляет конкретное сообщение Сервису B, который вызывает Сервис C.

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

Традиционная пирамида тестирования (unit > integration > E2E) по-прежнему применима, но требует адаптации для распределённых систем.

Уровень 1: Модульные тесты (Unit Tests)

Тестируют отдельные функции и классы внутри одного сервиса. Быстрые, многочисленные и выполняются без внешних зависимостей.

Сервис A
├── Unit-тесты бизнес-логики
├── Unit-тесты трансформации данных
└── Unit-тесты правил валидации

Цель: Убедиться, что внутренняя логика каждого сервиса корректна.

Уровень 2: Компонентные тесты (Component Tests)

Этот уровень уникален для микросервисов. Компонентный тест проверяет один сервис целиком, а его внешние зависимости (базы данных, другие сервисы) заменяются test double-ами.

┌─────────────────────────┐
│    Компонентный тест     │
│  ┌───────────────────┐  │
│  │   Сервис A         │  │
│  │   (реальный код)   │  │
│  └───────────────────┘  │
│  ┌─────────┐ ┌────────┐ │
│  │ Mock БД │ │Mock Сер│ │
│  │         │ │  вис B │ │
│  └─────────┘ └────────┘ │
└─────────────────────────┘

Компонентные тесты запускают сервис, отправляют HTTP-запросы к его реальным endpoint-ам и проверяют ответы. Отличие от интеграционных тестов в том, что все внешние вызовы замоканы.

Пример с in-memory базой данных:

describe('User Service Component', () => {
  let app;

  beforeAll(async () => {
    app = await startService({
      database: 'sqlite::memory:',
      paymentService: mockPaymentService,
      emailService: mockEmailService,
    });
  });

  test('POST /users создаёт пользователя и возвращает 201', async () => {
    const response = await request(app)
      .post('/users')
      .send({ name: 'Alice', email: 'alice@example.com' });

    expect(response.status).toBe(201);
    expect(response.body.name).toBe('Alice');
  });

  test('POST /users с дублирующим email возвращает 409', async () => {
    await request(app)
      .post('/users')
      .send({ name: 'Alice', email: 'alice@example.com' });

    const response = await request(app)
      .post('/users')
      .send({ name: 'Bob', email: 'alice@example.com' });

    expect(response.status).toBe(409);
  });
});

Уровень 3: Интеграционные тесты

Проверяют, что два или более сервиса корректно взаимодействуют. Используют реальные (или контейнеризованные) зависимости.

Интеграция сервис-сервис:

  • Сервис A вызывает API Сервиса B — совпадает ли формат запроса?
  • Сервис A публикует событие — Сервис B корректно его потребляет?

Интеграция сервис-инфраструктура:

  • Сервис A подключается к PostgreSQL — работают ли запросы?
  • Сервис A публикует в Kafka — доставляются ли сообщения?
  • Сервис A читает из кеша Redis — соблюдаются ли TTL?

Уровень 4: Контрактные тесты

Контрактные тесты проверяют, что интерфейс между двумя сервисами остаётся совместимым. Потребитель определяет, что ожидает; провайдер подтверждает, что может выполнить эти ожидания.

Этот уровень критичен в микросервисах, потому что сервисы развёртываются независимо. Без контрактных тестов Сервис-Провайдер может задеплоить ломающее изменение, о котором Сервис-Потребитель узнает только в production.

Уровень 5: Сквозные тесты (E2E Tests)

E2E-тесты проверяют полные бизнес-потоки через множество сервисов. Они наиболее реалистичны, но также наиболее хрупки, медленны и дороги.

Минимизируйте E2E-тесты. Покрывайте только критические бизнес-пути: регистрация, оплата, ключевые workflow-ы.

Testing Honeycomb

Некоторые команды предпочитают testing honeycomb вместо пирамиды для микросервисов. В этой модели интеграционные и компонентные тесты — самый широкий уровень, а не unit-тесты. Обоснование: в микросервисах большинство багов возникают на границах сервисов, а не внутри бизнес-логики.

Стратегии тестирования по паттерну коммуникации

Синхронный (REST/gRPC)

Когда Сервис A делает HTTP или gRPC вызов к Сервису B:

  1. Компонентный тест: Мокаем ответ Сервиса B в тестах Сервиса A.
  2. Контрактный тест: Проверяем совпадение схем request/response.
  3. Интеграционный тест: Поднимаем оба сервиса (Docker Compose) и проверяем реальный вызов.

Асинхронный (события/сообщения)

Когда Сервис A публикует событие, которое потребляет Сервис B:

  1. Компонентный тест: Проверяем, что Сервис A публикует событие правильной формы. Проверяем, что Сервис B корректно обрабатывает данное событие.
  2. Контрактный тест: Определяем схему события как контракт.
  3. Интеграционный тест: Публикуем реальное событие и проверяем обработку Сервисом B.

Хореография vs Оркестрация

В хореографии сервисы реагируют на события независимо — тестирование требует проверки цепочки событий. В оркестрации центральный координатор (паттерн Saga) управляет потоком — тестирование оркестратора становится приоритетом.

Стратегия окружений

Локальная разработка

Используйте Docker Compose для запуска зависимых сервисов локально:

services:
  user-service:
    build: ./user-service
    ports: ["3001:3001"]
    depends_on: [postgres, redis]
  order-service:
    build: ./order-service
    ports: ["3002:3002"]
    depends_on: [postgres, kafka]
  postgres:
    image: postgres:16
  redis:
    image: redis:7
  kafka:
    image: confluentinc/cp-kafka:7.5.0

CI Pipeline

Запускайте компонентные и интеграционные тесты в CI с помощью Docker-контейнеров. E2E-тесты держите в отдельном stage, который выполняется реже (ночью или на release-ветках).

Упражнение: Проектирование стратегии тестирования микросервисов

Вы — QA Lead платформы электронной коммерции со следующими микросервисами:

Архитектура системы

┌──────────┐     ┌──────────┐     ┌──────────┐
│  API     │────▶│  User    │────▶│  Auth    │
│  Gateway │     │  Service │     │  Service │
└──────────┘     └──────────┘     └──────────┘
      │
      ▼
┌──────────┐     ┌──────────┐     ┌───────────────┐
│  Order   │────▶│ Payment  │────▶│ Notification  │
│  Service │     │  Service │     │  Service      │
└──────────┘     └──────────┘     └───────────────┘
      │
      ▼
┌──────────┐     ┌──────────┐
│ Inventory│────▶│ Shipping │
│  Service │     │  Service │
└──────────┘     └──────────┘

Паттерны коммуникации:

  • API Gateway → Сервисы: REST (синхронный)
  • Order Service → Payment Service: REST (синхронный)
  • Order Service → Inventory Service: события Kafka (асинхронный)
  • Payment Service → Notification Service: события Kafka (асинхронный)

Задание 1: Распределение по уровням тестирования

Для каждого уровня тестирования перечислите конкретные тесты:

Unit-тесты (по сервису):

СервисЧто тестировать unit-тестами
Order ServiceРасчёт итога заказа, логика скидок, машина состояний заказа
Payment ServiceВалидация платежа, расчёт возврата, конвертация валют
Inventory ServiceЛогика проверки остатков, истечение резерваций
Notification ServiceРендеринг шаблонов, выбор канала (email/SMS/push)

Компонентные тесты:

Для каждого сервиса определите 3-5 компонентных тестов.

Контрактные тесты:

Определите пары потребитель-провайдер:

ПотребительПровайдерКонтракт
Order ServicePayment ServicePOST /payments: схема request/response
Order ServiceInventory ServiceСхема события ReserveStock
Payment ServiceNotification ServiceСхема события PaymentConfirmed

E2E-тесты (только критические пути):

  1. Пользователь регистрируется → входит → создаёт заказ → оплата успешна → запасы обновлены → доставка инициирована → уведомление отправлено.
  2. Пользователь создаёт заказ → оплата не проходит → заказ отменён → запасы высвобождены.
  3. Пользователь создаёт заказ → запрашивает возврат → возврат обработан → уведомление отправлено.

Задание 2: Сценарии отказов

Для каждого сервиса определите, что происходит при отказе и как это тестировать:

Отказ сервисаВлияниеСтратегия тестирования
Payment ServiceЗаказы не могут быть завершеныПроверить, что Order Service ставит платежи в очередь на повтор
Inventory ServiceПроверка остатков не работаетПроверить, что Order Service отклоняет или ставит заказы в очередь
Notification ServiceПользователи не уведомленыПроверить, что заказы всё равно завершаются (уведомление некритично)

Задание 3: Проектирование CI Pipeline

Спроектируйте CI pipeline с такими этапами:

Этап 1: Сборка и unit-тесты (по сервису, параллельно) — 2 мин
Этап 2: Компонентные тесты (по сервису, параллельно) — 5 мин
Этап 3: Контрактные тесты (потребитель + провайдер, параллельно) — 3 мин
Этап 4: Интеграционные тесты (Docker Compose) — 10 мин
Этап 5: E2E Smoke-тесты (staging) — 15 мин (только ночью)

Результаты

  1. Документ стратегии тестирования с количеством тестов по уровню и сервису.
  2. Диаграмма CI pipeline с этапами тестирования и gate-ами.
  3. Матрица рисков с приоритизацией отказов сервисов для тестирования.