Контрактне тестування
1) Де застосовувати контракти
HTTP REST/JSON: ресурси, пагінація, фільтри, ідемпотентність, коди помилок.
gRPC/Protobuf: типи повідомлень, статуси, семантика'deadline', backward-compat в.proto.
GraphQL: схеми, non-null, директиви, пермішені на поля.
Повідомлення/стріми (Kafka/Pulsar/SQS): event-схеми (Avro/JSON/Protobuf), ключі партіонування, порядок, ідемпотентні ключі.
Внутрішні SDK/бібліотеки: публічні функції/виключення/контракти продуктивності.
2) Модель CDC: ролі та артефакти
Споживач публікує контракт очікувань (приблизні запити/відповіді, матчери типів, інваріанти).
Постачальник проганяє верифікацію контрактів проти свого сервісу/адаптера/handler'ів.
Брокер контрактів (Pact Broker/Backstage/артефакт-репо) зберігає версії, теги ('prod','staging','canary') і матрицю сумісності'consumer @v → provider @v'.
Політика релізу: деплою провайдера заборонений, якщо порушується будь-який «прод-релевантний» контракт.
3) Що фіксувати в контракті (HTTP приклад)
Мінімум:- Метод/шлях/параметри/заголовки (вкл. auth, ідемпотентний ключ).
- Тіло і типові матчери (тип/формат/регексп/діапазони).
- Коди і структура помилок; стабільні'error _ code'.
- Семантичні інваріанти: сортування, унікальність, монотонність'created _ at'.
- Нефункціональні очікування (опціонально): p95, ліміти розміру, заголовки rate-limit.
json
{
"interaction": "GET /v1/users/{id}",
"request": { "method": "GET", "path": "/v1/users/123", "headers": {"Accept":"application/json"} },
"matchers": {
"response.body.id": "type:number",
"response.body.email": "regex:^.+@.+\\..+$",
"response.body.created_at": "format:rfc3339"
},
"response": {
"status": 200,
"headers": {"Content-Type":"application/json"},
"body": {"id": 123, "email": "alice@example.com", "created_at": "2025-10-31T12:00:00Z"}
},
"error_cases": [
{
"name":"not_found",
"request":{"path":"/v1/users/9999"},
"response":{"status":404, "body":{"error_code":"USER_NOT_FOUND"}}
}
]
}
4) Контракти для подій (event-driven)
Схема події: `type`, `version`, `id`, `occurred_at_utc`, `producer`, `subject`, `payload`.
Інваріанти: незмінність'id'і ідемпотентність по'( type, id)', порядок в межах ключа партії, монотонність'sequence'.
Schema Registry: зберігає еволюцію і правила сумісності (backward/forward/full).
Контракт-тести консьюмера: реплеять «золоті» події і фази негативів (невідомі поля, nullable).
json
{
"type":"record","name":"UserRegistered","namespace":"events.v1",
"fields":[
{"name":"id","type":"string"},
{"name":"occurred_at_utc","type":{"type":"long","logicalType":"timestamp-millis"}},
{"name":"email","type":"string"},
{"name":"marketing_opt_in","type":["null","boolean"],"default":null}
]
}
5) Еволюція і сумісність
Версії контрактів: семантика'MAJOR. MINOR. PATCH'( MAJOR - ламаючі).
Правила для REST:- Не ламайте: не видаляйте поля, не змінюйте тип/значення «error _ code».
- Додавайте опціональні поля з дефолтом; нові ендпоінти замість «магії».
- Депрекація: оголошення, паралельна підтримка, видалення за метриками.
- GraphQL: поля тільки додавати, non-null вводити через фази; директиви депрекації.
- gRPC/Proto: не перевикористати номери полів; тільки додавати нові з optional.
- Events: схема «vN»; консьюмери зобов'язані ігнорувати невідомі поля (ленієнтність).
6) Негативні та інваріантні перевірки
Negative: неправильні типи, заборонені значення, конфліктні параметри, перевищення лімітів.
Invariants: сортування відповідей, унікальність'id', коректність'next _ cursor', стабільність ідемпотентної відповіді при повторі.
Контракти тимчасових аспектів: 'created _ at'RFC3339/UTC, коректна проекція локальної доби не є частиною транспортного контракту - виноситься в бізнес-інваріанти.
7) Стаб-генерація і локальна розробка
З контрактів генеруються стаби провайдера для розробки споживача.
Для подій - генератори «валідних/прикордонних» повідомлень за схемою.
Стаби позначаються версією контракту і датою складання; заборонена публікація в прод.
8) Вбудовування в CI/CD (референс-пайплайн)
1. Consumer CI:
Лінт/збірка → генерація контрактів → юніт/контракт-тести → публікація в contract-broker (tag: `consumer@1. 7. 0`).
2. Provider CI:
Підняття сервісу локально/в контейнері → фетч релевантних контрактів ('prod '/' staging') → верифікація → публікація статусу в broker.
3. Release Gate:
Деплою провайдера блокується, якщо є невиконані контракти.
4. Nightly Matrix:
Матриця сумісності'consumer versions × provider versions'; звіти і тривоги.
9) Приклади практик по доменах
9. 1 REST: пагінація курсорами (контрактний інваріант)
Відповідь містить'items []','next _ cursor'( nullable),'limit','total'( опціонально).
Інваріанти: 'len (items) ≤ limit', повторний виклик з тим же'cursor'→ ідемпотентний набір.
Помилка, якщо одночасно задані «cursor» і «page».
9. 2 Ідемпотентність POST
Контракт вимагає заголовок'Idempotency-Key'.
Інваріант: повторний запит з тим же ключем повертає той же'id '/статус.
9. 3 Події: гарантії порядку
Ключ партіонування в контракті: `partition_key = user_id`.
Інваріант: «sequence» монотонно зростає всередині ключа; консьюмер зобов'язаний обробляти повтори.
10) Безпека і приватність в контрактах
Не включати ПДн/секрети в приклади - тільки синтетика.
Фіксувати обов'язкові заголовки безпеки: `Authorization`, `X-Signature`, `Replay-Prevention`.
Для вебхуків - контракт підпису і відповіді «2хх »/ретраїв.
У логах контракт-тестів - маскування чутливих полів.
11) Інструменти
Pact/Pactflow/Pact Broker - HTTP/Message контракти, матриця сумісності.
OpenAPI/AsyncAPI - специфікації + тест-генератори (Dredd, Schemathesis).
Karate/REST Assured - сценарні перевірки контрактів REST.
Protobuf/gRPC -'buf','protolint', тести сумісності; Schema Registry для Avro/JSON/Proto в потоках.
Conformance-тести для GraphQL (graphql-compat), snapshot тести схем.
12) Псевдокод верифікації провайдера (спрощено)
python def verify_contract(provider, contract):
for case in contract["cases"]:
req = build_request(case["request"])
res = provider.handle(req) # локально/контейнер assert match_status(res.status, case["response"]["status"])
assert match_headers(res.headers, case["response"].get("headers", {}))
assert match_body(res.body, case["matchers"], allow_extra_fields=True)
for neg in contract.get("error_cases", []):
res = provider.handle(build_request(neg["request"]))
assert res.status == neg["response"]["status"]
assert res.json.get("error_code") == neg["response"]["body"]["error_code"]
13) Анти-патерни
«Скріншоти Postman - це контракт»: немає версій/типових матчерів/автоматичної валідації.
Оверснейпінг: контракт фіксує точні значення замість типів/патернів → помилкові падіння.
Один загальний контракт для різних регіонів/каналів: ігнорує варіативність (прапори, geo-правила).
Контракти без брокера/матриці: не можна зрозуміти, які версії сумісні.
Ставка на e2e замість контрактів: повільно, дорого, нестабільно.
Відсутність негативних/інваріантних кейсів: тестується тільки «зелена доріжка».
14) Спостережуваність і експлуатація
Експорт статусу в broker + дашборд «health контрактів».
Алерти: нові падіння провайдера проти'prod'-контрактів, зростання «unknown field» в подіях.
Трасування: 'contract _ id','version','decision _ id'в логах верифікації.
15) Процес депрекації
1. Додати поле/ендпоінт (не ламає).
2. Відзначити старе як «deprecated» в специфікації, оголосити терміни.
3. Відстежувати споживачів за логами/брокеру; міграційні гайди.
4. Включити «тіньовий» deny в стейджі (dry-run), потім enforce.
5. Видалити після нульової частки використання і підтвердження сумісності.
16) Чек-лист архітектора
1. Визначено споживачів та їх власників? Контракти версіонуються?
2. Є broker і матриця сумісності з тегами середовищ?
3. У контракт включені негативи та інваріанти (ідіempotентність, курсори, сортування)?
4. Для подій налаштований Schema Registry і режим сумісності?
5. Пайплайн блокує реліз провайдера при порушенні прод-контрактів?
6. Описано процес депрекації і політика еволюції?
7. Генеруються стаби з контрактів, є локальні генератори подій?
8. Маскування ПД і обов'язкові заголовки безпеки задокументовані?
9. Метрики/алерти за контрактами підключені, є звіти по дрифту?
10. Контракти перевіряються в CI у обох сторін (consumer і provider)?
Висновок
Контрактне тестування переносить «істину» про взаємодії у версіоновані артефакти і робить інтеграції передбачуваними. CDC, брокер контрактів і дисципліна еволюції схем замінюють «ламаючі сюрпризи» на керований процес: швидкі перевірки, чіткі інваріанти і прозора сумісність версій. Це знижує вартість e2e, прискорює релізи і покращує якість всієї платформи.