open atlas
↑ К треку
Инженерная практика ENG · 02 · 09

Контрактное тестирование: чтение кода и контрактов

Читай настоящие Pact consumer-тесты, настройку provider verification и diff поломки контракта, предсказывай поведение и выбирай фикс с наибольшим рычагом.

ENG Senior ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior

Контракты читают в коде и diff’ах, не в прозе. Прочитай consumer-тест, обвязку provider verification и настоящую поломку контракта, затем выбери фикс, который сениор сделал бы первым.

Цель

Отработай цикл, который ты проходишь в каждом контрактном инциденте: прочитать тест, предсказать, что утвердит сгенерированный pact, и взяться за фикс, держащий гейт точным — без повторной связки двух команд.

Сниппет 1 — consumer-тест

// тест консьюмера checkout-web (Pact JS)
const { like, integer, string } = MatchersV3;

provider
  .given("a price with id 42 exists")
  .uponReceiving("a request for price 42")
  .withRequest({ method: "GET", path: "/prices/42" })
  .willRespondWith({
    status: 200,
    body: like({
      amount_cents: integer(1299),
      currency: string("USD"),
    }),
  });

await provider.executeTest(async (mock) => {
  const res = await pricingClient(mock.url).getPrice(42);
  expect(res.amountCents).toBe(1299);   // читает amount_cents
});
Викторина

Что pact, сгенерированный этим тестом, реально утверждает про amount_cents и каково следствие?

Сниппет 2 — настройка provider verification

// верификация провайдера pricing (Pact JS)
await new Verifier({
  provider: "pricing",
  providerBaseUrl: "http://localhost:8080",
  pactBrokerUrl: process.env.PACT_BROKER_URL,
  publishVerificationResult: true,
  providerVersion: process.env.GIT_SHA,
  // stateHandlers: { ... }   // <-- намеренно не подключены
}).verifyProvider();
Викторина

Pact потребителя выше использует given('a price with id 42 exists'), но у этого верификатора нет stateHandlers. Что произойдёт и каков фикс?

Сниппет 3 — diff поломки контракта

  // сериализатор ответа провайдера pricing
  {
    "id": price.id,
-   "amount_cents": price.amountCents,
+   "price_minor_units": price.amountCents,
    "currency": price.currency
  }
Викторина

Три развёрнутых потребителя читают amount_cents. Это переименование едет в одном PR. Собственные unit-тесты провайдера зелёные. Что делает контрактный гейт и как переименование надо реально выкатывать?

Сниппет 4 — гейт can-i-deploy

# в пайплайне деплоя консьюмера, привязан к отгружаемому коммиту
pact-broker can-i-deploy \
  --pacticipant checkout-web --version "$GIT_SHA" \
  --to-environment production
# exit 0 => деплой; non-zero => заблокировано
# ...затем, в самом конце успешного rollout:
pact-broker record-deployment \
  --pacticipant checkout-web --version "$GIT_SHA" \
  --environment production
Викторина

Почему record-deployment должен запускаться в самом конце успешного rollout и почему can-i-deploy привязан к git SHA, а не к semver?

Итог

Каждый контрактный инцидент читается в коде: type matcher’ы consumer-теста задают, что pact реально утверждает (тип, не пример); отсутствующий stateHandler валит верификацию из-за предусловия, которое провайдер не засеял, а не из-за сломанного контракта; переименование потребляемого поля в одном PR валит верификацию для каждого потребителя и должно ехать как expand-then-contract; а гейт деплоя работает, только когда версии — git SHA и record-deployment запускается в конце успешного rollout, чтобы матрица отражала реальность. Прочитай тест и diff, предскажи поведение гейта, затем чини на том уровне, что держит гейт и точным, и развязанным.

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.