Контрактное тестирование API
1) Зачем контрактное тестирование
Контракт фиксирует ожидания клиента и обещания провайдера: маршруты/методы, заголовки, схемы тел, статусы, семантика ошибок и ограничения. Цель — ловить несовместимые изменения до интеграции и выпускать версии безопасно, без тяжелых E2E.
2) Подходы
Consumer-Driven Contracts (CDC): клиент формирует ожидания (Pact и аналоги); провайдер регулярно их верифицирует.
Спецификационный: контракт как единый источник истины (OpenAPI/Protobuf/GraphQL SDL); тесты валидируют реализацию против спецификации.
Событийный: схемы сообщений (Avro/JSON Schema/Protobuf) + правила совместимости в брокере/реестре.
3) HTTP/REST: спецификационный поток
1. Контракт: OpenAPI 3.x (схемы, примеры, коды).
2. Линт и статанализ: стиль, обязательные поля, единые коды.
3. Валидация реализации: генератор тестов против OpenAPI (schemathesis/Dredd-подход) + ручные негативы.
4. Снапшоты: фиксируйте примеры ответов, ETag семантику, заголовки идемпотентности.
yaml paths:
/v1/payments:
post:
responses:
"201": { $ref: "#/components/responses/PaymentCreated" }
"409": { description: Duplicate by Idempotency-Key }
"422": { description: Schema/Business validation failed }
4) CDC (Pact-подход)
Жизненный цикл:1. Consumer пишет тест, формирует pact-файл (ожидания).
2. Публикация в брокер (артефакт).
3. Provider в CI поднимает сервис (или контракт-стойку), верифицирует pact’ы.
4. Брокер вычисляет can-i-deploy матрицу совместимости.
Пример consumer-теста (псевдо-JS):js pact
.given("wallet exists")
.uponReceiving("get wallet")
.withRequest({ method:"GET", path:"/v1/wallets/w123", headers:{ "Authorization": term({generate:"Bearer x", matcher:/^Bearer\s.+/}) }})
.willRespondWith({
status: 200,
headers: { "Content-Type": "application/json" },
body: like({ id:"w123", currency: "EUR", balance: 0 })
});
5) gRPC/Protobuf контракты
Контракт — `.proto` с версионированием пакетов/служб.
Совместимость: не переиспользовать теги, только добавлять новые с optional, не удалять используемые поля; резервировать номера.
Тесты: генерация стаба сервера/клиента, прокат автосгенеренных кейсов + негативы (unknown fields, size-limits).
6) Событийные контракты (Kafka/NATS/…)
Схемы: Avro/JSON Schema/Protobuf в Schema Registry.
Политика совместимости: `BACKWARD` (часто достаточно) или `FULL`.
Тесты продьюсера: валидирует сообщение против схемы; тесты консьюмера: принимает старую и новую версию.
Инварианты: ключи идемпотентности, порядок/повторяемость, семантика дедупликации.
json
{"type":"record","name":"PayoutCreated","fields":[
{"name":"payoutId","type":"string"},
{"name":"amount","type":"double"},
{"name":"currency","type":{"type":"string","logicalType":"iso-4217"}}
]}
7) Совместимость и версии
Backward-compatible (рекомендуемый минимум): добавляйте необязательные поля, не ломайте существующие.
Forward-compatible: потребители игнорируют неизвестные поля.
Full: и то, и другое.
Версионирование: `path (/v1)`, `Accept: application/vnd.brand.v2+json`, `proto package v2`.
Deprecation-политика: окно вывода (например, 90 дней), предупреждающие заголовки/события.
8) Негативные и ошибки — это тоже контракт
Стандартизируйте коды: 400/401/403/404/409/422/429/5xx, обязательные поля `code`, `message`, `trace_id`.
Размеры/лимиты — часть контракта (413/414/431).
Идемпотентность: поведение при повторе (409 vs 201 same id).
Заголовки: `Retry-After` при 429/503, `Idempotency-Key`, `Content-Language` и т. п.
json
{ "code":"validation_error", "message":"amount must be ≥ 1", "trace_id":"..." }
9) Данными управляет контракт
Примеры/примерочные ответы (examples) — живые, валидируются в CI.
Фикстуры для CDC — минимальные, детерминированные.
Генерация данных — property-based для чисел/дат; но не ломать стабильность снапшотов.
10) Пайплайн в CI/CD (референс)
1. Lint/validate: OpenAPI/Proto/Avro (`validate`, style-линер).
2. Publish: контракт как артефакт (брокер/реестр).
3. Verify: провайдер прогоняет CDC-пакеты/спецификационные тесты.
4. can-i-deploy gate: без зеленой матрицы релиз запрещен.
5. Diff: проверка, что изменения совместимы (semantic diff).
6. Report: JUnit/HTML, список нарушений/дефиниций.
yaml jobs:
lint:...
publish-contract: needs: [lint]
verify-provider: needs: [publish-contract]
can-i-deploy:
if: always()
steps: [run: pact-broker can-i-deploy...]
11) Инструменты (по классам задач)
Линт/валидаторы: openapi-linters, protobuf-lint, avro-tools.
CDC: Pact-семейство/брокер, Spring Cloud Contract, Hoverfly (HTTP записи/реплеи).
Спецификационные раннеры: schemathesis/Dredd-подобный подход, Postman test + JSON Schema.
Diff: semantic-diff OpenAPI/Proto/Avro (выявляет breaking-изменения).
Контейнеры: Testcontainers для поднятия провайдера/стойки контрактов.
12) Антипаттерны
«Спец-док» отдельно от кода → рассинхронизация. Храните контракт рядом с сервисом.
Моки стороннего сервиса вместо контракта → хрупкость при апдейтах.
Случайные ответы/генерация без привязки к схеме → флейки.
Изменение типов/обязательности полей без версии.
Silent-расширения без уведомлений/депрекации.
Отсутствие негативных контрактов и кодов ошибок.
13) Специфика iGaming/финансов
Формализуйте денежные поля: `amount` — decimal с масштабом, валюта — ISO-4217, инварианты сумм.
Контракты платежей/вебхуков: HMAC/mTLS, anti-replay (`X-Timestamp` окно), идемпотентность, `Retry-After`.
Региональность/тенанты: обязательные заголовки `X-Tenant/X-Region`, локализация сообщений.
События: неизменяемые журналы (audit), ключи дедупликации, гарантии доставки (at least once + идемпотентные хендлеры).
14) Примеры «скелетов» тестов
14.1 Schemathesis-стиль (псевдо)
bash schemathesis run openapi. yaml --checks all --hypothesis-deadline=200
14.2 Postman как спецификационный раннер
js pm. test ("Scheme/v1/wallets/{ id} is valid," () => {
const schema = pm. collectionVariables. get("wallet_get_schema");
pm. expect(ajv. validate(JSON. parse(schema), pm. response. json())). to. be. true;
});
14.3 Provider-верификация CDC (псевдо)
bash pact-broker can-i-deploy --pacticipant wallets --version $SHA --to-environment staging
15) Чек-лист prod-готовности
- Контракты в репозитории, CI валидирует и публикует артефакты.
- CDC включен для критичных интеграций; работает broker/matrix «can-i-deploy».
- Политика совместимости (HTTP/gRPC/Events) задокументирована; автоматический semantic-diff.
- Негативные контракты: ошибки, лимиты размеров, идемпотентность, `Retry-After`.
- Тест-данные детерминированы; снапшоты примеров поддерживаются.
- Версионирование и deprecation: сроки, уведомления, заголовки.
- Для событий — Schema Registry и режим совместимости; продьюсер/консьюмер тестируют обе версии.
- Артефакты: JUnit/HTML, отчеты diff/verify, матрица совместимости.
- Инцидент-процедура: быстрый rollback контракта/фича-флаг, уведомление интеграторам.
16) TL;DR
Фиксируйте ожидания в контрактах и прогоняйте их автоматически: CDC для клиентских ожиданий, спецификационные тесты для соответствия реализации, реестр схем для событий. Держите строгую политику совместимости и негативные контракты (ошибки, лимиты, идемпотентность). Нельзя релизить без зеленой can-i-deploy матрицы и semantic-diff без breaking-изменений.