Что проверяет контрактное тестирование
Что обе стороны ожидают одну и ту же структуру. Имена полей, типы, обязательность и необязательность, коды состояния. Если поставщик удалит поле, от которого зависит потребитель, контрактное тестирование упадёт на сборке, а не в три часа ночи.
Для системы из множества сервисов с независимыми развёртываниями это действительно ценно и намного дешевле сквозных интеграционных окружений.
Что оно намеренно не проверяет
Верно ли значение. Ответ правильной структуры с неверной итоговой суммой удовлетворяет любому контракту.
Работает ли сценарий. Каждый вызов может соответствовать своему контракту, а вся последовательность — давать бессмысленный результат.
Что видит пользователь. Контракты — про то, как сервисы общаются с сервисами. Продукт при этом никто не открывал.
Всё это не претензия. Контрактный тест, который проверял бы значения, оказался бы завязан на данные и ломался бы постоянно, — а именно этого контрактное тестирование и должно было избежать. Узость охвата здесь и есть замысел.
Типичный сценарий провала
Команда внедряет контрактное тестирование, интеграционные сбои прекращаются, уверенность растёт. Через полгода в продакшене ломается то, что проходило все контрактные тесты: структура была верной, а поведение — нет. Обычно отсюда делают вывод, что контрактное тестирование не сработало, и это неверный вывод.
Контрактное тестирование сделало свою работу. Пробел был в другом: поведение не проверял никто, а успех контрактного слоя лишь делал этот пробел менее заметным.
Что должно стоять рядом с ним
Функциональная проверка на работающих сервисах: даёт ли последовательность вызовов правильный результат, корректно ли отрабатывают ошибочные ветки, выдерживает ли авторизация. Это другой слой, и он отвечает на вопросы, недоступные контрактам.
Такие проверки должны уметь работать с сессиями, значениями, которые переносятся между вызовами, порядком выполнения и очисткой данных, — всё это описано в документации по тестированию API.
Терминал
npm install -g @testsprite/testsprite-cli
testsprite setup
Если ставить ничего не хочется, то же самое можно сделать в панели управления. Всё остальное, на что способна командная строка, описано в репозитории CLI.
Контракты со стороны потребителя и почему они обычно выигрывают
Два подхода различаются тем, кто пишет контракт, и разница проявляется не сразу, а через год.
Контракты со стороны поставщика фиксируют то, что сервис намерен предоставлять. Это полезно, но обычно получается слишком щедро: контракт описывает ответ целиком, включая поля, которые никто не читает. Ломающие изменения в неиспользуемых полях роняют сборку, команды начинают игнорировать падения контрактных тестов — и весь смысл теряется.
Контракты со стороны потребителя фиксируют то, что каждый потребитель действительно читает. Контракт получается меньше, падения — настоящими, и вдобавок появляется по-настоящему полезный побочный эффект: точная карта того, какие поля кому нужны. Именно эта карта позволяет поставщику уверенно объявлять что-то устаревшим, а это и есть то, чего все на самом деле хотели.
Где запускается каждый из них
Контрактные тесты — на сборке, быстро и на каждом коммите, потому что вся их ценность в том, чтобы поймать расхождение структур до интеграции. Проверка поведения — на развёрнутом окружении, на каждом pull request.
Триггер важнее механизма. Если привязать запуск к событию развёртывания, каждое изменение проверяется без чьего-либо отдельного решения: GitHub App делает это из панели управления, а шаг GitHub Actions — внутри вашего рабочего процесса.
На какие вопросы отвечает TestSprite, а контракты — нет
Верны ли значения и даёт ли последовательность правильный результат. TestSprite обращается к запущенным сервисам через их интерфейсы, поэтому ответ, который удовлетворяет всем контрактам, но содержит неверную итоговую сумму, будет пойман.
Многошаговые сценарии — это и есть тот самый пробел. Integration Tests связывают вызовы в цепочку, где один ответ питает следующий, а Dependency Chains выводят безопасный порядок из того, что каждый тест-кейс требует и что создаёт. Auto-Authentication поддерживает сессии живыми на протяжении всего прогона, Dynamic Variables переносят значения между вызовами, Dependency Chains выводят порядок выполнения из того, что каждый тест-кейс требует и что создаёт, а Auto-Cleanup удаляет ровно то, что создал прогон.
В итоге получаются два непересекающихся слоя: контракты ловят расхождения структур на сборке, дёшево и на каждом коммите, а проверка поведения закрывает вопросы корректности, к которым контракты намеренно слепы.
Занимается ли TestSprite контрактным тестированием?
Нет. Он проверяет поведение на запущенных сервисах. Контрактное тестирование — другой инструмент, и они дополняют друг друга.
Достаточно ли контрактного тестирования для микросервисов?
Это правильный ответ на расхождения структур между сервисами, но он не говорит, даёт ли сценарий из нескольких сервисов верный результат.
Со стороны потребителя или со стороны поставщика?
Подход со стороны потребителя ловит больше реальных поломок, потому что фиксирует то, что потребители действительно используют, а не то, что поставщик собирался предоставлять.
Нужны ли нам всё ещё интеграционные тесты?
Их станет меньше, в этом и смысл. Оставьте небольшой набор для сценариев, проходящих через несколько сервисов, а вопрос структуры отдайте контрактам.
А если обе стороны под нашим контролем?
Ценность падает, потому что менять обе стороны можно одновременно. Больше всего пользы там, где команды развёртываются независимо.
Структура верна, поведение неизвестно.
Контрактное тестирование дёшево ловит расхождения структур между сервисами и намеренно слепо к тому, правильно ли работает каждая из сторон. Держите его на этапе сборки, а рядом поставьте проверку поведения на запущенных сервисах.