Знакомство с контрактными тестами (Contract Tests/Pact)

11-11-2019

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

Интерфейсы между разными приложения могут быть реализованы в разных форматах и технологиях. Самые распространённые:

  • REST и JSON через HTTPS;
  • RPC;
  • построение событийно-ориентированной архитектуры с использованием очередей.

Каждый интерфейс задействует две стороны: поставщика и потребителя.

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

В мире REST поставщик создаёт REST API со всеми необходимыми конечными точками, а потребитель обращается к этому REST API, чтобы получить данные или инициировать изменения в другой службе. В асинхронном мире событийно-ориентированной архитектуры поставщик (часто именуемый издателем) публикует данные в очередь; а потребитель (часто называемый подписчиком) подписывается на эти очереди, считывает и обрабатывает данные. Поскольку сервисы поставщика и потребителя распределяются по разным командам, то вы оказываетесь в ситуации, когда нужно чётко указать интерфейс между ними (так называемый контракт). Традиционно компании подходят к этой проблеме следующим образом:

  1. Написать длинную и подробную спецификацию интерфейса (контракт).
  2. Реализовать сервис поставщика согласно определённому контракту.
  3. Передать спецификации интерфейса стороне потребителя.
  4. Подождать, пока они реализуют свою часть интерфейса.
  5. Запустить крупномасштабный ручной системный тест, чтобы всё проверить.
  6. Надеяться, что обе команды будут всегда соблюдать определения интерфейса и не облажаются

Более современные компании заменили шаги 5 и 6 на автоматизированные контрактные тесты, которые проверяют, что реализации на стороне потребителя и поставщика всё ещё придерживаются определённого контракта. Они выступают хорошим набором регрессионных тестов и гарантируют раннее обнаружение отклонения от контракта.

Что такое CDC (Consumer Driven Contracts)?

Три ключевых элемента:

  • Контракт. Описывается с помощью некоторого DSL, зависит от реализации. Он содержит в себе описание API в виде сценариев взаимодействия: если пришел определенный запрос, то клиент должен получить определенный ответ.
  • Тесты клиентов. Причем в них используется заглушка, которая автоматически формируется из контракта.
  • Тесты для API. Они также генерируются из контракта.

Таким образом, контракт — исполняемый. И основная особенность подхода заключается в том, что требования к поведению API идут upstream, от клиента к серверу.

Контракт фокусируется на том поведении, которое действительно важно потребителю. Делает явными его допущения относительно API.

Главная задача CDC — сблизить понимание поведения API его разработчиками и разработчиками его клиентов. Этот подход хорошо сочетается с BDD. В конечном счёте, этот контракт так же служит:

  • улучшению коммуникаций;
  • разделению общего понимания проблемной области;
  • реализации решения внутри и между командами.

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

Команда поставщика должна непрерывно получать и выполнять эти CDC-тесты (в своём конвейере сборки), чтобы немедленно замечать любые критические изменения. Если они нарушают интерфейс, то их CDC-тесты не пройдут, предотвращая критические изменения. Пока тесты проходят, команда может вносить любые изменения, какие хочет, не беспокоясь о других командах. Ориентированный на потребителя контрактный подход сокращает процесс разработки до следующего:

  • Команда потребителя пишет автоматизированные тесты со всеми ожиданиями со стороны потребителей.
  • Они публикуют тесты для команды поставщика.
  • Команда поставщика непрерывно запускает CDC-тесты и следит за ними.
  • Команды немедленно вступают в переговоры, когда CDC-тесты ломаются.

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

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

Pact, вероятно, самый известный среди них. Он предлагает утончённый подход к написанию тестов для потребителя и поставщика, предоставляет заглушки для отдельных служб и позволяет обмениваться CDC-тестами с другими командами. Pact портирован на множество платформ и может использоваться с языками JVM, Ruby, .NET, JavaScript и многими другими. Pact — разумный выбор, чтобы начать работу с CDC. Его документация помогает получить твёрдое понимание CDC, что в свою очередь облегчает Вам задачу пропагандировать CDC для работы с другими командами. Ориентированные на пользователя контрактные тесты могут кардинально упростить работу автономных команд, которые начнут действовать быстро и уверенно. Качественный набор CDC-тестов неоценим, чтобы быстро продолжать разработку, не ломая прочие сервисы и не огорчая другие команды.

Применимость CDC

Какие ограничения есть у Consumer Driven Contracts?

  • За использование такого подхода приходится платить дополнительными инструментами вроде pact. Сами по себе контракты — это дополнительный артефакт, ещё одна абстракция, которую нужно аккуратно поддерживать, осознанно применять к ней инженерные практики.
  • Они не заменяют e2e тесты, так как заглушки всё равно остаются заглушками — моделями реальных компонентов системы, которые может и чуть-чуть, но не соответствуют действительности. Через них не проверить сложные сценарии.
  • Также CDC не заменяют функциональные тесты API. Они дороже в поддержке, чем Plain Old Unit Tests. Разработчики Pact рекомендуют пользоваться следующей эвристикой — если убрать контракт и это не вызовет ошибки или неправильную интерпретацию со стороны клиента — значит он не нужен. К примеру, не нужно через контракт описывать абсолютно все коды ошибок API, если клиент обрабатывает их одинаково. Другими словами, контракт описывает для сервиса только то, что важно его клиенту. Не больше, но и не меньше.

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

Заключение

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

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

Литература

  1. The Practical Test Pyramid
  2. За всё ответишь! Consumer Driven Contracts глазами разработчика