Нещодавнє розгортання призвело до збою трьох мікросервісів, хоча кожен юніт-тест, інтеграційний тест і перевірка на mock-сервері пройшли успішно. Команда замінила свої API-моки на контрактне тестування. За шість місяців кількість контрактів зросла з трьох до 47, а щомісячний рівень помилок інтеграції впав з двох інцидентів до нуля.
Чому моки не можуть вас захистити
Mock-сервер лише імітує структуру, яку очікує споживач; він ніколи не перевіряє, чи насправді постачальник надає саме таку структуру. Якщо постачальник перейменовує поле — наприклад, із name на display_name — мок все одно повертає старе корисне навантаження, тести споживача залишаються «зеленими», а жива система падає. Збій у продакшені у вівторок о 14:00 був саме таким: мок «збрехав» щодо реального контракту.
Контрактне тестування заповнює цю прогалину
Контрактне тестування змушує дві сторони API узгодити спільне визначення ще до того, як будь-який код потрапить у продакшен. Існує два поширені підходи:
- Контракти, керовані споживачем (Consumer-driven contracts) — сервіс-споживач описує свої очікування; сервіс-постачальник перевіряє їх на відповідність. Це добре працює для внутрішніх мікросервісів, що розвиваються разом.
- Контракти, керовані постачальником (Provider-driven contracts) — постачальник публікує специфікацію; споживачі перевіряють свій код на відповідність їй. Це звичний патерн для публічних API.
Перший підхід зазвичай запобігає порушенням інтеграції всередині мікросервісної архітектури.
Як працює контракт, керований споживачем
- Споживач пише тест, який точно описує те, що йому потрібно від постачальника.
- Запуск тесту створює pact-файл — JSON-документ, який фіксує ці очікування.
- Постачальник запускає свій реальний сервіс проти pact-файлу у своєму CI-конвеєрі.
- Якщо постачальник змінює поле, верифікація не проходить, і збірка блокується.
Оскільки верифікація виконується на реальному коді постачальника, будь-яка критична зміна виявляється на ранньому етапі, а не після розгортання.
Місце контрактних тестів у вашій піраміді тестування
- Юніт-тести — швидкі, тестують ізольовану логіку.
- Контрактні тести — середньої швидкості, підтверджують дотримання API-домовленостей.
- E2E-тести — повільні, перевіряють повні бізнес-процеси.
Ставтеся до контрактних тестів як до сполучної ланки між швидким зворотним зв'язком юніт-тестів та широким покриттям E2E-тестів. Орієнтуйтеся на точки інтеграції, які ламаються найчастіше, і почніть із двох або трьох критично важливих ендпоінтів.
Реальна історія впровадження
Команда, про яку йдеться в цій статті, почала з трьох контрактів, що охоплювали їхні найбільш вразливі виклики. Через шість місяців вони мали 47 контрактів, що охоплювали більшу частину міжсервісного трафіку. За цей період кількість інцидентів, пов'язаних із порушенням API, впала з двох на місяць до нуля.
Коли контрактне тестування може не вартувати зусиль
- Ви — розробник-одинак, який тримає всі сервіси в одному репозиторії.
- API є винятково стабільним і не змінювався роками.
- Ви створюєте тимчасовий прототип, який скоро буде викинуто.
У таких сценаріях накладні витрати на підтримку контрактів можуть перевищити вигоду.
Потенційні недоліки та способи їх пом'якшення
- Версіонуйте контракти разом із кодом, який вони описують.
- Автоматизуйте верифікацію під час кожного запуску CI, щоб уникнути застарілих контрактів.
- Переглядайте зміни контрактів у pull requests, щоб вчасно помічати випадкові порушення.
Висновок
Якщо ви досі покладаєтеся на вручну створені моки, щоб переконати себе, що ваші сервіси можуть взаємодіяти, ви робите ставку на хибну обіцянку. Контрактне тестування перетворює цю ставку на верифіковану угоду, виявляючи критичні зміни до того, як вони потраплять у продакшен, і, як показують цифри команди, може повністю усунути помилки інтеграції.
