Контрактное тестирование: чтение кода и контрактов
Суть Читай настоящие Pact consumer-тесты, настройку provider verification и diff поломки контракта, предсказывай поведение и выбирай фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Контракты читают в коде и diff’ах, не в прозе. Прочитай consumer-тест, обвязку provider verification и настоящую поломку контракта, затем выбери фикс, который сениор сделал бы первым.
Цель
Отработай цикл, который ты проходишь в каждом контрактном инциденте: прочитать тест, предсказать, что утвердит сгенерированный pact, и взяться за фикс, держащий гейт точным — без повторной связки двух команд.
Что pact, сгенерированный этим тестом, реально утверждает про amount_cents и каково следствие?
Heads-up integer(1299) — type matcher; 1299 — пример, не запиненное значение. Пиннинг переспецифицировал бы и падал на безобидных изменениях цены — та хрупкость, ради избегания которой и существуют matcher'ы.
Heads-up Pact записывает взаимодействие, которое клиент потребителя реально выполнил против mock'а. expect() охраняет тест, но именно matcher в willRespondWith задаёт записанное типовое ограничение.
Heads-up given() записывает строку provider-state для настройки во время верификации — он ничего не утверждает про хранимые значения. Body matcher задаёт форму ответа; пример — не утверждение про данные.
Pact потребителя выше использует given('a price with id 42 exists'), но у этого верификатора нет stateHandlers. Что произойдёт и каков фикс?
Heads-up Верификатор никогда не пишет в хранилище провайдера из примеров pact; они описывают ожидаемый ответ. Без обработчика данные не засеяны, и повторённый запрос не может вернуть 200.
Heads-up Взаимодействия с состояниями не пропускаются — они проигрываются, и предусловие просто не выполнено, поэтому они падают. Пропуск тихо снёс бы покрытие; Pact прогоняет их и сообщает о сбое.
Heads-up publishVerificationResult лишь управляет тем, отправляется ли результат pass/fail в брокер — он не делает падающее взаимодействие проходящим. Отсутствующий обработчик всё равно валит сравнение.
Три развёрнутых потребителя читают amount_cents. Это переименование едет в одном PR. Собственные unit-тесты провайдера зелёные. Что делает контрактный гейт и как переименование надо реально выкатывать?
Heads-up Собственные тесты провайдера не знают, что три потребителя читают amount_cents — это исходный пробел e2e. Верификация против pact'ов потребителей падает, потому что старого имени поля больше нет в ответе.
Heads-up Lockstep-координация flag-day — ровно та боль, ради устранения которой существуют контракты. expand-then-contract даёт каждой стороне двигаться независимо, держа обе формы живыми в окне миграции.
Heads-up Pending — для новых ожиданий потребителя, которые провайдер ещё не построил, а не для маскировки настоящего breaking change полей, от которых зависят потребители. Пометка реальной поломки как pending воссоздаёт аварию.
Сниппет 4 — гейт can-i-deploy
# in the consumer's deploy pipeline, keyed to the commit being shippedpact-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
Викторина
Completed
Почему record-deployment должен запускаться в самом конце успешного rollout и почему can-i-deploy привязан к git SHA, а не к semver?
Heads-up Порядок важен: деплой, записанный до упавшего rollout, заставляет брокер верить, что в prod стоит несуществующая версия. А ценность SHA — уникальность на коммит, не длина.
Heads-up can-i-deploy читает то, что уже развёрнуто до этого деплоя, поэтому запись идёт после. И SHA вообще не сортируются по времени — их ценность в точном отслеживании контракта.
Heads-up Матрица не может узнать, что где развёрнуто, из одной верификации — верификация это попарный факт. Именно record-deployment сообщает брокеру, какая версия провайдера реально в каждом окружении.
Итог
Каждый контрактный инцидент читается в коде: type matcher’ы consumer-теста задают, что pact реально утверждает (тип, не пример); отсутствующий stateHandler валит верификацию из-за предусловия, которое провайдер не засеял, а не из-за сломанного контракта; переименование потребляемого поля в одном PR валит верификацию для каждого потребителя и должно ехать как expand-then-contract; а гейт деплоя работает, только когда версии — git SHA и record-deployment запускается в конце успешного rollout, чтобы матрица отражала реальность. Прочитай тест и diff, предскажи поведение гейта, затем чини на том уровне, что держит гейт и точным, и развязанным.