Недавний деплой привел к поломке трех микросервисов, хотя все юнит-тесты, интеграционные тесты и проверки на mock-серверах прошли успешно. Команда заменила свои API-моки на контрактное тестирование. За шесть месяцев количество контрактов выросло с трех до 47, а ежемесячная частота сбоев интеграции снизилась с двух инцидентов до нуля.

Почему моки не могут вас защитить

Mock-сервер лишь имитирует структуру, которую ожидает потребитель; он никогда не проверяет, что поставщик действительно отдает именно такую структуру. Если поставщик переименует поле — например, с name на display_name — мок по-прежнему будет возвращать старую полезную нагрузку, тесты потребителя останутся «зелеными», а живая система упадет. Сбой в продакшене во вторник в 14:00 был именно таким: мок «солгал» о реальном контракте.

Контрактное тестирование восполняет этот пробел

Контрактное тестирование заставляет две стороны API прийти к общему определению еще до того, как какой-либо код попадет в продакшен. Существует два распространенных подхода:

  • Consumer-driven contracts (контракты, управляемые потребителем) — сервис-потребитель описывает свои ожидания, а сервис-поставщик проверяет их на соответствие. Это хорошо работает для внутренних микросервисов, которые развиваются синхронно.
  • Provider-driven contracts (контракты, управляемые поставщиком) — поставщик публикует спецификацию, а потребители проверяют свой код на соответствие ей. Это стандартный паттерн для публичных API.

Первый подход обычно предотвращает поломки интеграций внутри микросервисной архитектуры.

Как работает контракт, управляемый потребителем

  1. Потребитель пишет тест, который в точности описывает то, что ему нужно от поставщика.
  2. Запуск теста генерирует pact-файл — JSON-документ, фиксирующий эти ожидания.
  3. Поставщик запускает свой реальный сервис против этого pact-файла в своем CI-конвейере.
  4. Если поставщик изменит поле, проверка не пройдет, и сборка будет заблокирована.

Поскольку проверка выполняется на реальном коде поставщика, любое ломающее изменение обнаруживается на раннем этапе, а не после деплоя.

Место контрактных тестов в вашей пирамиде тестирования

  • Unit-тесты — быстрые, тестируют изолированную логику.
  • Контрактные тесты — средней скорости, подтверждают соблюдение соглашений API.
  • End-to-end тесты — медленные, проверяют полные бизнес-сценарии.

Относитесь к контрактным тестам как к мосту между быстрой обратной связью от юнит-тестов и широким охватом E2E-тестов. Сосредоточьтесь на точках интеграции, которые ломаются чаще всего, и начните с двух или трех критически важных эндпоинтов.

История внедрения в реальных условиях

Команда, о которой идет речь в этой статье, начала с трех контрактов, покрывающих их самые хрупкие вызовы. Спустя шесть месяцев у них было уже 47 контрактов, охватывающих большую часть межсервисного трафика. За этот период количество инцидентов, связанных с поломкой API, снизилось с двух в месяц до нуля.

Когда контрактное тестирование может не стоить затраченных усилий

  • Вы — соло-разработчик, и все сервисы находятся в одном репозитории.
  • API исключительно стабилен и не менялся годами.
  • Вы создаете временный прототип, который будет быстро выброшен.

В таких сценариях накладные расходы на поддержку контрактов могут перевесить пользу.

Потенциальные недостатки и способы их минимизации

  • Храните версии контрактов вместе с кодом, который они описывают.
  • Автоматизируйте проверку при каждом запуске CI, чтобы избежать использования устаревших контрактов.
  • Проверяйте изменения контрактов в pull requests, чтобы вовремя заметить случайные поломки.

Итог

Если вы все еще полагаетесь на самодельные моки, чтобы убедить себя, что ваши сервисы могут взаимодействовать, вы делаете ставку на ложное обещание. Контрактное тестирование превращает эту ставку в проверяемое соглашение, отлавливая ломающие изменения до того, как они достигнут продакшена, и, как показывают цифры команды, может полностью исключить сбои интеграции.