Сложности тестирования микросервисов
Архитектуры микросервисов разбивают монолитное приложение на десятки или сотни независимо развёртываемых сервисов. Каждый сервис владеет своими данными, общается по сети и может быть написан на другом языке. Это даёт гибкость развёртывания, но драматически увеличивает сложность тестирования.
В монолите вы тестируете одно приложение. В микросервисах вы тестируете множество сервисов и взаимодействия между ними. Баг может не существовать ни в одном отдельном сервисе — он может проявиться только когда Сервис 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:
- Компонентный тест: Мокаем ответ Сервиса B в тестах Сервиса A.
- Контрактный тест: Проверяем совпадение схем request/response.
- Интеграционный тест: Поднимаем оба сервиса (Docker Compose) и проверяем реальный вызов.
Асинхронный (события/сообщения)
Когда Сервис A публикует событие, которое потребляет Сервис B:
- Компонентный тест: Проверяем, что Сервис A публикует событие правильной формы. Проверяем, что Сервис B корректно обрабатывает данное событие.
- Контрактный тест: Определяем схему события как контракт.
- Интеграционный тест: Публикуем реальное событие и проверяем обработку Сервисом 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 Service | Payment Service | POST /payments: схема request/response |
| Order Service | Inventory Service | Схема события ReserveStock |
| Payment Service | Notification Service | Схема события PaymentConfirmed |
E2E-тесты (только критические пути):
- Пользователь регистрируется → входит → создаёт заказ → оплата успешна → запасы обновлены → доставка инициирована → уведомление отправлено.
- Пользователь создаёт заказ → оплата не проходит → заказ отменён → запасы высвобождены.
- Пользователь создаёт заказ → запрашивает возврат → возврат обработан → уведомление отправлено.
Задание 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 мин (только ночью)
Результаты
- Документ стратегии тестирования с количеством тестов по уровню и сервису.
- Диаграмма CI pipeline с этапами тестирования и gate-ами.
- Матрица рисков с приоритизацией отказов сервисов для тестирования.