Разделы портала

Онлайн-тренинги

.
Введение в тестирование контрактов, часть 2: общая информация о тестировании контрактов
22.03.2022 00:00

Автор: Баз Дейкстра (Bas Dijkstra)
Оригинал статьи
Перевод: Ольга Алифанова

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

Что осложняет интеграционное тестирование?

Предыдущая статья содержала ряд проблем, усложняющих интеграционное тестирование в нашем случае:

  • Для проведения end-to-end тестов нужно сложное тест-окружение, состоящие из всех задействованных компонентов. Оно должно предоставляться каждый раз, когда нужно проводить эти тесты.
  • Разные компоненты разрабатываются разными командами, и у каждой – свое расписание, свой бэклог. Это значит, что командам часто приходится ждать друг друга, чтобы провести интеграционное тестирование.
  • Не всегда понятно, кто отвечает за интеграцию, потому что масштаб этих тестов пересекает границы разных команд.
  • Так как команда Address находится на другом конце страны, есть коммуникационный барьер – нельзя просто взять и подсесть за их стол. Большая часть коммуникации ведется через почту, Slack и Jira, и в ходе этого теряется масса информации.

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

Чем шире масштаб интеграционного теста, тем больше компонентов в нем задействовано, и тем сложнее будет настройка подходящего тест-окружения. Наша ситуация намеренно ограничена, но в реальном мире у нас на руках могут быть десятки или даже иногда сотни компонентов, формирующих тестируемое приложение, и все из них должны быть доступными каждый раз, когда запускается тест. Неудивительно, что многим командам и компаниям трудно добиться достаточного интеграционного и end-to-end тестового покрытия!

Введение в тестирование контрактов

В игру вступает тестирование контрактов. Цель тестирования контрактов – трансформировать интеграционное тестирование, сделав его из синхронного асинхронным. Вместо того, чтобы поднимать крупные участки, а иногда даже все приложение целиком, чтобы запустить тесты, тестирование контрактов фокусируется на индивидуальных парах компонентов – потребителе и провайдере.

В любой распределенной системе компоненты совместно работают, обмениваясь данными. Потребитель запрашивает данные у провайдера (или отправляет данные провайдеру, или обновляет или удаляет существующие данные), а провайдер отвечает или запрошенными данными (или подтверждающим сообщением), или подходящим ответом, информирующим, что что-то пошло не так.


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


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

Чтобы детальнее узнать о концепции тестирования контрактов, прочитайте эту статью.

Как работает тестирование контрактов?

Тестирование контрактов, как следует из его имени, использует контракты для формализации ожиданий всех пар "потребитель – провайдер" по отношению к поведению "второй половины". В целом оно делится на два типа, и основное различие между ними в том, кто отвечает за создание контракта:

  • В тестировании контрактов, ориентированных на потребителя, контракт создает потребитель. Этот контракт содержит ожидания потребителя о поведении провайдера в ответ на специфические запросы. Задача провайдера – доказать, что он соответствует формализованным в контракте ожиданиям.
  • В тестировании контрактов, ориентированных на провайдера, контракт создает провайдер. Он больше похож на спецификацию в стиле "вот что я умею". Потребители могут использовать контракты провайдера, чтобы убедиться в способности успешно обрабатывать разные типы ответов от него.

У обоих подходов есть плюсы и минусы, некоторые из которых описаны здесь. В следующей части статьи и последующих статьях мы будем рассматривать только тестирование контрактов, ориентированных на потребителя (CDCT). Это также самый часто используемый на практике вид контрактного тестирования, и инструменты, которые мы будем рассматривать, тоже написаны для поддержки именно CDCT/

Полный цикл CDCT, покрывающий верификацию, что пара потребитель-провайдер может взаимодействовать, состоит из четырех шагов:

  1. Потребитель генерирует контракт, содержащий ожидания от поведения провайдера.
  2. Потребитель публикует контракт для приемки провайдером.
  3. Провайдер берет контракт и убеждается, что текущая реализация соответствует высказанным потребителем ожиданиям.
  4. Провайдер публикует результаты верификации с целью информирования потребителя.


Этот процесс и делает CDCT (и тестирование контрактов в целом) асинхронным методом интеграционного тестирования: потребитель и провайдер выполняют шаги, за которые они отвечают, в ходе своего процесса разработки, не нуждаясь в доступности связанных с ними компонентов. Все ожидания потребителей от провайдеров письменно зафиксированы в контракте, и каждая сторона проводит интеграционное тестирование в ходе своего собственного процесса разработки и релиза.

В качестве примера пройдем по этим шагам более детально, используя пример из нашего магазина бутербродов. В этом примере мы сконцентрируемся на Customer API в качестве потребителя, и Address API в качестве провайдера. Пожалуйста, посмотрите предыдущую статью для детального объяснения, что делают эти компоненты. Если конкретнее, мы возьмем GET-операцию, доступную через Address API и инициируемую Customer API для получения подробной информации о конкретном адресе, ассоциированном с покупателем:

GET /address/{id}

Пока что предположим, что нас интересуют только HTTP-коды ответов, возвращаемых операцией GET. В следующей статье мы подробно разберем другие ожидания Customer API от ответа Address API – в частности, данные, возвращаемые им.

В плане HTTP-кодов статуса у Customer API могут быть следующие ожидания:

  • "Когда я запрашиваю данные, используя существующий в базе {id}, я ожидаю, что кодом ответа будет HTTP 200".
  • "Когда я запрашиваю данные, используя правильно отформатированный {id}, отсутствующий в базе, я ожидаю, что кодом ответа будет HTTP 404".
  • "Когда я запрашиваю данные, используя неверный формат {id}, я ожидаю, что кодом ответа будет HTTP 400".

На первом шаге процесса CDCT Customer API создает контракт, содержащий эти ожидания в стандартизированном формате. Customer API затем публикует этот контракт в централизованном месте, где провайдер, в данном случае Address API, может забрать его для верификации. На третьем шаге провайдер проверяет, соответствует ли его текущая реализация всем формализованным в контракте ожиданиям, и, наконец, сообщает результаты верификации в централизованном месте, чтобы потребитель узнал, что:

a)       Провайдер соответствует всем ожиданиям и все хорошо, или

b)      Провайдер не может соответствовать всем ожиданиям, и нужно начать переговоры, чтобы разрешить проблему интеграции.

Как можно видеть на этом примере, контракт – это соглашение между единственным потребителем (Customer API в данном случае) и единственным провайдером (Address API в нашем примере). Это означает, что если компонентом провайдера пользуется Х разных потребителей, и все из них внедрили CDCT, провайдеру нужно соответствовать ожиданиям Х(*) разных контрактов, чтобы убедиться в отсутствии интеграционных проблем. Очевидно, что чем больше потребителей, тем выше риск потенциальных конфликтов интересов. Мы подробно разберем пример такого конфликта в дальнейших статьях.

(*) В примере с нашим магазином бутербродов Х равен 2, так как Address API используется и Customer API, и Order API.

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

В следующей статье мы (наконец-то!) погрузимся в код и увидим, как внедрять шаги CDCT, описанные здесь, используя Pact.

Обсудить в форуме