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

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

.
Введение в тестирование контрактов, часть 6: двусторонне направленные контракты
28.07.2022 00:00

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

В предыдущих статьях серии о тестировании ориентированных на потребителя контрактов и Pact мы обсуждали CDCT, разобрались, как использовать Pact для поддержки CDCT, как автоматизировать процесс и сделать его частью процессов CI/CD, и посмотрели, что будет, когда на стороне потребителя меняются ожидания.

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

Предупреждение: двусторонне направленное тестирование контрактов существует только в Pactflow и недоступно в OSS Pact Broker.

Новый игрок: поставщик сервиса оплаты

Чтобы оформление заказов было бесперебойным и бесшовным, наш магазин бутербродов решил позволить заказчику оплачивать заказы онлайн через стороннего провайдера оплаты. Этот сервис оплаты используется сервисом заказов, который мы уже рассмотрели в прошлых статьях, и наша архитектура теперь выглядит как-то так:


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

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

По словам команды Pactflow, это довольно распространенная ситуация в командах, думающих о внедрении CDCT: работа с Pact способом, описанным в предыдущей статье, требует значительных усилий на стороне как потребителя, так и поставщика.

В базу кода нужно добавлять новые зависимости, написать определения и тесты для Pact, добавить в билд дополнительные шаги… Это и удерживает множество команд от внедрения контрактного тестирования.

Недавно команда Pactflow выпустила решение этой проблемы, упрощающее командам начало работы с тестированием контрактов. Встречайте – двусторонне направленное тестирование контрактов.

Двусторонне направленное тестирование контрактов

В "традиционном" CDCT потребитель генерирует контракт, используя Pact, а затем передает его провайдеру для верификации, используя Pact Broker. Провайдер забирает контракт, проводит верификацию и загружает результаты верификации обратно в Pact Broker. Используя can-i-deploy, потребитель и провайдер могут проверить, безопасно ли выпускать новую версию в прод.

Двусторонне направленное тестирование контрактов, или BDCT, использует другой алгоритм: в BDCT и потребитель, и провайдер генерируют свои версии контрактов и загружают их в Pactflow, а он затем проверяет оба контракта на совместимость.

Как только это сделано, и потребитель, и провайдер могут снова воспользоваться can-i-deploy перед выходом на прод, чтобы проверить наличие проблем интеграции.

Для упрощения генерации контрактов BDCT не требует "полного" внедрения Pact. Вместо этого можно воспользоваться существующими тестами, инструментами и спецификациями, внедрив их в решение для контрактного тестирования.

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

Сторона потребителя – генерирование контракта из макетов WireMock

Для проверки процесса оплаты заказа бутербродов команда заказов уже пользуется WireMock, имитируя провайдера сервиса оплаты с его помощью. Так как WireMock – инструмент, который уже поддерживается командой Pactflow в BDCT, имитируемое им поведение можно использовать для генерации контракта на стороне потребителя.

В этом примере мы посмотрим на операции HTTP GET, которые получают детали оплаты для конкретного ID заказа. Для других операций процесс аналогичен (к примеру, для POST-операции передачи платежа за заказ).

Вот как будет выглядеть тест успешного получения деталей платежа:

private static final UUID ID = UUID.fromString("8383a7c3-f831-4f4d-a0a9-015165148af5");
private static final UUID ORDER_ID = UUID.fromString("228aa55c-393c-411b-9410-4a995480e78e");
private static final String STATUS = "payment_complete";
private static final int AMOUNT = 42;
private static final String DESCRIPTION = String.format("Payment for order %s", ORDER_ID);
 
@Autowired
private WireMockServer wireMockServer;
 
@Test
public void getPayment_validOrderId_shouldYieldExpectedPayment() {
 
     Payment payment = new Payment(ID, ORDER_ID, STATUS, AMOUNT, DESCRIPTION);
 
     String paymentAsJson = new Gson().toJson(payment);
 
     wireMockServer.stubFor(WireMock.get(WireMock.urlEqualTo(String.format("/payment/%s", ORDER_ID)))
                .willReturn(aResponse().withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                          .withBody(paymentAsJson)));
 
     Payment responsePayment = new PaymentServiceClient(wireMockServer.baseUrl())
           .getPaymentForOrder(ORDER_ID.toString());
 
     assertThat(responsePayment.getId()).isEqualTo(ID);
     assertThat(responsePayment.getOrderId()).isEqualTo(ORDER_ID);
     assertThat(responsePayment.getStatus()).isEqualTo(STATUS);
     assertThat(responsePayment.getAmount()).isEqualTo(AMOUNT);
     assertThat(responsePayment.getDescription()).isEqualTo(DESCRIPTION);
}

Схожие тесты есть для ситуации, когда платеж для заказа не найден (возвращается HTTP 404 с пустым телом ответа), и ситуации, когда ID заказа невалиден (возвращается HTTP 400, также с пустым телом).

Для генерации BDCT-контракта потребителя из этих тестов и макетов команда Pactflow разработала библиотеку wiremock-pact-generator. После добавления этой зависимости в проект нам остается только поменять код тестов, добавив WireMockPactGenerator как слушатель в экземпляр WireMock:

@BeforeAll
void configureWiremockPactGenerator() {
 
     wireMockServer.addMockServiceRequestListener(
                WireMockPactGenerator
                                .builder("order_consumer", "payment_provider")
                                .build());
}

Можно видеть, что при добавлении WireMockPactGenerator нам нужно только идентифицировать потребителя (order_consumer) и провайдера (payment_provider), и все готово к работе.

При запуске тестов, вызывающих экземпляр WireMock со слушателем Pact, генерируется контракт, который размещается в папке /target/pacts – так же, как и в "традиционном" CDCT. Контракт, однако, выглядит чуть иначе, особенно в части, задающей ожидания для тела ответа в случае успешного получения деталей платежа:

As you can see, when adding the WireMockPactGenerator, the only thing we need to supply is an identification for the consumer (order_consumer) and the provider (payment_provider) and we’re good to go.

"response": {
     "status": 200,
      "headers": {
            "content-type": "application/json"
    },
    "body": {
           "id": "8383a7c3-f831-4f4d-a0a9-015165148af5",
           "orderId": "228aa55c-393c-411b-9410-4a995480e78e",
           "status": "payment_complete",
          "amount": 42,
          "description": "Payment for order 228aa55c-393c-411b-9410-4a995480e78e"
      }
}

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

В BDCT, как обсуждалось выше, верификация осуществляется Pactflow, а не провайдером (так как он загружает свой собственный контракт), и верификация на основе схемы (то есть верификация типа или формы данных) – это единственный тип выполняемой верификации.

Иными словами, Pactflow сравнивает форму ожидаемого ответа (выданного потребителем) с формой реального ответа (выданной провайдером) и сообщает о несоответствиях, если они найдены.

У такого поведения есть ряд интересных преимуществ (отметим, что это прямая цитата из письма Мэтта и крайне отзывчивой команды Pact):

  1. Можно создавать больше сценариев, чем вы обычно создаете (потому что большее количество примеров не увеличит нагрузку на тест провайдера). Это полезно, так как многих путает четкая область применения контрактного теста, и иногда они чересчур на этом зацикливаются.
  2. Потребители могут добавлять новые ожидания для провайдера, и если эти ожидания соответствуют существующему известному контракту провайдера, можно идти в деплой, не ожидая новой верификации от провайдера. По сути вы можете добавить абсолютно нового потребителя, и если он потребляет только подмножество API провайдера, все можно сделать, вообще не ставя провайдера в известность!
  3. Состояния провайдера исчезают – это мощная идея, но новички, похоже, мучаются с этим больше всего. Поэтому этот процесс куда легче понять людям, которые сталкиваются с ним впервые.
  4. Следствие пункта 2 – процесс CI упрощается. Так как провайдер просто загружает описание API, нет нужны приводить в действие веб-хуки для получения результатов верификации от провайдера и организовывать свои процессы. Инициация билда от провайдера не даст никакой новой информации.

Как только контракт сгенерирован из тестов WireMock на стороне потребителя, их можно загружать, как и традиционные контракты:

mvn pact:publish

Смотря на Pactflow, можно увидеть, что добавилась новая интеграция между order_consumer и payment_provider, и она еще не верифицирована:


Вот как это выглядит на стороне потребителя. Перейдем на сторону провайдера.

Сторона провайдера – использование существующей спецификации API в качестве контракта

Используя BDCT для быстрого внедрения контрактных тестов, провайдеры могут повторно использовать существующие сервисные спецификации в качестве контракта для верификации в Pactflow. На данный момент поддерживаются только спецификации OpenAPI (OAS), но это довольно распространенный стандарт спецификаций, и множество провайдеров уже могут выиграть от этой возможности.

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

Как правило, CI-процесс провайдера выполняет эти функциональные тесты до публикации контракта/OAS, что означает, что упавшие тесты воспрепятствуют загрузке контракта в Pactflow.

На момент создания этой статьи загрузка OAS в Pactflow проводится через довольно заковыристый вызов API, однако команда Pactflow работает над включением этого в инструментарий Pactflow CLI, что, вне всяких сомнений, упростит процесс.

Вот вызов PUT, который сейчас требуется для загрузки контракта провайдера (как можно видеть, я воспользовался Postman, однако cUrl или любой другой клиент API тоже сработают):

PUT /contracts/provider/<<PROVIDER_NAME>>/version/<<PROVIDER_VERSION>> HTTP/1.1
Content-Type: application/json
Authorization: Bearer <<YOUR TOKEN GOES HERE>>
User-Agent: PostmanRuntime/7.26.8
Accept: */*
Cache-Control: no-cache
Postman-Token: d2ea82f0-5335-485a-834b-01a01fa2d0e0
Host: ota.pactflow.io
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 2569
{
     "content": "<<BASE64 ENCODED YML FILE GOES HERE>>",
     "contractType": "oas",
     "contentType": "application/yaml",
      "verificationResults": {
          "success": true,
          "content": "<<BASE64 ENCODED ADDITIONAL TEST RESULTS GO HERE>>",
          "contentType": "text/plain",
          "verifier": "verifier"
     }
}

Если все прошло успешно, мы получим код ответа HTTP 201, сообщающий, что контракт провайдера успешно загружен в Pactflow. Обновив экран, мы увидим, что Pactflow не только получил контракт, но и сравнил контракты потребителя и провайдера:


В этом случае контракт потребителя (сгенерированный из макета WireMock спецификаций API) и контракт провайдера (OAS) совместимы, и это значит, что order_consumer и payment_provider могут воспользоваться can-i-deploy для проверки текущего статуса верификации контракта перед выходом на прод.

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

Итак, какой же подход выбрать? BDCT или CDCT?

Как обычно, единственный верный ответ тут – "зависит". Отвечу вам этим сравнением, созданным командой Pact.

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

Весь код статьи находится в GitHub.

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