Ответ
Да, применял контрактное тестирование, в частности с помощью фреймворка Pact, для решения проблем интеграции в микросервисной архитектуре. Мы столкнулись с ситуацией, когда сервис-потребитель (consumer) и сервис-провайдер (provider) разрабатывались разными командами, и частые breaking changes в API провайдера ломали потребителя.
Как это работало на практике:
-
Со стороны потребителя (Consumer Tests): В наших автотестах для сервиса-потребителя мы использовали Pact для мокирования провайдера на основе ожидаемого контракта.
// Пример на JavaScript (Jest + Pact) const { Pact } = require('@pact-foundation/pact'); describe('User Service', () => { const provider = new Pact({ consumer: 'FrontendService', provider: 'UserApiService', }); beforeAll(() => provider.setup()); afterEach(() => provider.verify()); afterAll(() => provider.finalize()); it('should receive user data', async () => { // Определяем ожидаемое взаимодействие (контракт) await provider.addInteraction({ state: 'a user with id 123 exists', uponReceiving: 'a request for user data', withRequest: { method: 'GET', path: '/users/123', }, willRespondWith: { status: 200, headers: { 'Content-Type': 'application/json' }, body: { id: 123, name: 'John Doe', // Ожидаемая структура ответа }, }, }); // Выполняем тест против мока Pact const user = await fetchUser(123); expect(user.name).toBe('John Doe'); }); });После успешного прогона тестов Pact публиковал контракт (JSON-файл) в общий брокер (например, PactFlow).
-
Со стороны провайдера (Provider Verification): В пайплайне CI сервиса-провайдера запускалась задача верификации. Она забирала опубликованные контракты и прогоняла реальный API провайдера, проверяя, что он удовлетворяет всем ожиданиям потребителей.
# Команда для верификации провайдера pact-verifier --provider-base-url http://localhost:8080 --pact-broker-url https://our-broker.example.com --provider "UserApiService" --consumer-version-tags "prod"
Результат: Это позволило обнаруживать несовместимости на этапе Pull Request, а не в продакшене. Провайдер видел, что его изменения ломают конкретного потребителя, и мог либо скорректировать API, либо согласовать изменение с командой потребителя. Контрактное тестирование не заменило end-to-end тесты, но значительно снизило количество интеграционных сбоев.
Ответ 18+ 🔞
Бля, ну вот реально, история знакомая до боли в жопе. Сидишь такой, пишешь свой сервис, а тут бац — и соседняя команда хуяк и поменяла API, даже не предупредив. И твой красивый код накрывается медным тазом, потому что вместо user_name теперь прилетает fullName. Удивление пиздец, а работать-то надо.
Так вот, пришлось нам внедрять эту вашу контрактную хуйню, а именно Pact. Суть в чём, ёпта? Чтобы потребитель и провайдер друг другу в глаза не смотрели, но договорённости нарушать не смели. Как в бандитской разборке, только для программистов.
Как мы это провернули на деле:
-
Со стороны того, кто пользуется (Consumer): В своих автотестах мы начали мокать провайдера не абы как, а через Pact. То есть прямо в коде прописываем: "слушай, провайдер, я ожидаю, что когда я тебе вот такую хуйню отправлю, ты мне вот такую структуру в ответ вывалишь".
// Пример на JavaScript (Jest + Pact) const { Pact } = require('@pact-foundation/pact'); describe('User Service', () => { const provider = new Pact({ consumer: 'FrontendService', provider: 'UserApiService', }); beforeAll(() => provider.setup()); afterEach(() => provider.verify()); afterAll(() => provider.finalize()); it('should receive user data', async () => { // Вот тут мы и диктуем свои условия, блядь await provider.addInteraction({ state: 'a user with id 123 exists', uponReceiving: 'a request for user data', withRequest: { method: 'GET', path: '/users/123', }, willRespondWith: { status: 200, headers: { 'Content-Type': 'application/json' }, body: { id: 123, name: 'John Doe', // Чётко говорим, что хотим видеть. Без сюрпризов! }, }, }); // И тестируем уже против этого мока const user = await fetchUser(123); expect(user.name).toBe('John Doe'); }); });И после того как тесты проходят, Pact выплевывает этот контракт (просто JSON-файл) в общее место — в так называемый брокер. Типа публичная оферта, ебать.
-
Со стороны того, кто предоставляет (Provider): А вот тут самое интересное начинается. В CI/CD пайплайне провайдера впендюрили задачу, которая берёт все эти контракты от потребителей и натягивает их на свой живой, блядовитый API. И смотрит — а удовлетворяет ли он всем этим ожиданиям? Если где-то накосячил и поменял ответ — сразу получает по шапке в пайплайне.
# Команда, которая всё и ловит за руку pact-verifier --provider-base-url http://localhost:8080 --pact-broker-url https://our-broker.example.com --provider "UserApiService" --consumer-version-tags "prod"
Что в сухом остатке, чувак? Волнение ебать, но терпения ноль — зато теперь мы ловим поломки не когда всё уже горит в продакшене, а прямо в пулл-реквесте. Провайдер видит: "Опа, а я тут поле name переименовал, а оказывается, FrontendService на него завязан и уже охуел". И можно либо откатиться, либо побежать договариваться. Это не отменяет толстые end-to-end тесты, но количество внезапных пиздецов в пятницу вечером сократилось овердохуища. И жить стало спокойнее, ядрёна вошь.