awesome-everything EN
↑ Обратно к восхождению

Инженерная практика

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

Суть Читай настоящие Pact consumer-тесты, настройку provider verification и diff поломки контракта, предсказывай поведение и выбирай фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

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

Цель

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

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

// checkout-web consumer test (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);   // reads amount_cents
});
Викторина

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

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

// pricing provider verification (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: { ... }   // <-- intentionally not wired
}).verifyProvider();
Викторина

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

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

  // pricing provider response serializer
  {
    "id": price.id,
-   "amount_cents": price.amountCents,
+   "price_minor_units": price.amountCents,
    "currency": price.currency
  }
Викторина

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

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

# in the consumer's deploy pipeline, keyed to the commit being shipped
pact-broker can-i-deploy \
  --pacticipant checkout-web --version "$GIT_SHA" \
  --to-environment production
# exit 0 => deploy; non-zero => blocked
# ...then, at the very end of a successful 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, предскажи поведение гейта, затем чини на том уровне, что держит гейт и точным, и развязанным.

Продолжить восхождение ↑Контрактное тестирование: построй гейт и переживи breaking-изменение
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.