Spring Cloud Contract. Что такое контрактное тестирование и с чем его едят
Тестирование является неотъемлемой частью процесса разработки ПО. Согласно пирамиде тестирования Майка Коэна как не сказать про пирамиду тестирования можно выделить следующие виды тестирования:
модульное тестирование (60%)
интеграционное тестирование (20%)
приемочное тестирование (15%)
сквозное тестирование (5%)
где % — это соотношение количества тестов, которое может различаться от проекта к проекту.
Контрактное тестирование или Consumer Driven Contract (CDC) является связующим звеном между модульным и интеграционным тестированием. Тут важно отметить скорость выполнения тестов, а именно, чем выше по пирамиде находятся тесты, тем ниже скорость и выше сложность тестируемого взаимодействия или тем больше можно проверить интеграцию с другими ПО. Таким образом, при контрактном тестировании выше необходимость в поиске проблемных мест за один прогон теста.
Итак, что же такое Consumer Driven Contract (CDC)?
Как известно, а также хорошо описано тут, каждый интерфейс имеет поставщика (supplier) и потребителя (consumer). Само собой, сервисы поставщика и потребителя распределены между разными командами, мы оказываемся в ситуации, когда чётко прописанный интерфейс между ними (или контракт) просто необходим. Обычно многие подходят к этой проблеме следующим образом:
Реализуют сервис поставщика согласно спецификации
Передают спецификацию интерфейса потребителю
Ждут реализации от другой стороны
Запускают ручные системные тесты, чтобы всё проверить
Держат кулачки, что обе стороны будут вечно соблюдать описанный интерфейс
Сегодня многие компании заменили последние два шага на автоматизированные контрактные тесты, которые регулярно проверяют соответствие описания и реализации у поставщика и потребителя определённого контракта. Что является набором регрессионных тестов, которые обеспечивают раннее обнаружение отклонения от контракта.
Разберемся во взаимодействии на примере REST архитектуры: поставщик создает API c некоторым endpoint, а потребитель отправляет запрос к API, например, с целью получения данных или выполнения изменений в другом приложении.
Это контракт, который описывается с помощью DSL (domain-specific language). Он включает API описание в форме сценариев взаимодействия между потребителем и поставщиком. С помощью CDC выполняется тестирование клиента и API с использованием заглушек, которые собираются на основе контракта. Основной задачей CDC является сближение восприятия между командами разработчиков API и разработчиков клиента. Таким образом, участники команды потребителей пишут CDC тесты (для всех данных проекта разработки), чтобы команда поставщика смогла запустить тесты и проверить API. В итоге команда поставщика с легкостью разработает свой API, используя тесты CDC. Результатом прогона контрактных тестов является понимание, что поставщик уверен в исправной работе API у потребителя.
Следует обратить внимание, что команда потребителя должна регулярно осуществлять поддержку CDC-тестов при каждом изменении, и вовремя передавать всю информацию команде поставщика. Если регулярно фиксируем неудачно выполненные CDC-тесты, то следует пойти (в буквальном смысле слова, к пострадавшей стороне теста и узнать, в рамках какой задачи были изменения (что привело к падению теста), а также уточнить, к чему в перспективе приведет данное изменение – прокачиваем софт скиллы:)). Из того, что было описано выше, можно выделить следующие тезисы для выполнения контрактного тестирования:
Команда разработчиков (тестировщиков) со стороны потребителей пишет автоматизированные тесты с ожидаемыми параметрами со стороны потребителей.
Тесты передаются команде поставщика.
Команда поставщика запускает контрактные тесты и проверяет результат их выполнения. Если происходит падение тестов, то команды должны зафиксировать сбой и перепроверить документацию (согласованность разработки).
Использование CDC-тестов является важным шагом в разработке микросервисной архитектуры приложений, т.к. позволяет использовать автономные группы тестов. Т.к. в данный момент микросервисная архитектура становится широко применяемой в IT, то подход с CDC-тестированием становится популярным и, что немаловажно, достаточно эффективным из-за своей позиции в иерархии видов тестов.
Минусы CDC
Было сказано много чего хорошего, но какие минусы есть у CDC? В целом сводятся в основном к следующим ограничениям:
CDC тесты не заменяют E2E тесты. По факту я склонен отнести CDC к заглушкам, которые являются моделями реальных компонентов, но не являются ими, т.е. это еще одна абстракция, которую нужно поддерживать и применять в нужных местах (сложно реализовать сложные сценарии).
CDC тесты не заменяют функциональные тесты API. Лично придерживаюсь золотого правила – если убрать контракт и это не вызывает ошибки или неправильную работу клиента, то значит он не нужен. Пример: Нет необходимости проверять все коды ошибок через контракт, если клиент обрабатывает их (ошибки) одинаково. Таким образом контракт то, что важно для клиента сервиса, а не наоборот.
CDC тесты дороже в поддержке, чем функциональные тесты.
Для реализации CDC-тестов нужно использовать (изучать) отдельные инструменты тестирования – Spring Cloud Contract, PACT.
Инструменты для CDC
Автор попробовал 2 тула Spring Cloud Contract и PACT. Protobuf можете изучить самостоятельно. Пришел к тому, что Spring Cloud Contract ему ближе Ниже в данной статье будет рассмотрен инструмент Spring Cloud Contract и будет приведен небольшой пример реализации контрактного теста. Если коротко об инструментах:
Перейдем к практике и разберем стандартный пример использования контрактного тестирования с помощью Spring Cloud Contract.
Для начала нужно описать поставщика и потребителя услуг. Подготовьте стандартный проект с Java 1.8, maven 3.6.3, JUnit 4.12.
Проект будет иметь следующую структуру:
Поставщик
Добавьте зависимости org.springframework.cloud:
Далее необходимо создать базовый класс для загрузки Spring context.
Заглушка с проверкой четного числа
Заглушка с проверкой нечетного числа
public void validate_shouldReturnEvenWhenRequestParamIsEven()
public void validate_shouldReturnOddWhenRequestParamIsOdd()
Результатом сборки также станет добавление заглушки в локальный репозиторий Maven, чтобы наши потребители могли ее использовать.
Потребитель:
Потребители контрактного тестирования поддерживают контракт через заглушки, сгенерированные через http-соединение, таким образом изменение в поставщике нарушит контракт и приведет к ошибке.
Добавим зависимости в pom файл потребителя (consumer):
Создадим RestController BasicMathController, который выполняет GET запрос для получения ответа от сгенерированной заглушки.
Теперь пришло время настроить наш stub Runner, который сообщит нашему потребителю имеющихся заглушек, который находятся в локальном репозитории Maven от поставщика.
Для запуска тестов выполните команду mvn clean install и проверьте результат.
Пример из кода можно найти на github.
Тестирование контракта потребителя сервиса — часть 1
Это первый блог из серии о тестированиях контракта потребителя сервиса. В этой серии представлена концепция и продемонстрировано написание тестов контрактов для приложения spring boot.
В мире микросервисов мы часто говорим об их преимуществах. Однако есть определенные условия, которые должны быть соблюдены, прежде чем выбрать этот архитектурный паттерн. Блог Мартина Фаулера очень хорошо освещает эти требования. Одним из условий, о которых он упоминает, является ContinuousDelivery. ContinuousDelivery — это технология разработки программного обеспечения, при которой вы создаете его таким образом, что оно может быть запущено в любое время. В контексте микросервисов непрерывная доставка (continuous delivery) означает, что любой сервис может быть внедрен в производство в любое время вне зависимости от остальных микросервисов. Но что гарантирует, что развертывание нового микросервиса не повлияет на общую функциональность приложения? Конечно, тестирование. Однако мы не хотим запускать все приложение, которое может состоять из сотен микросервисов, только для того, чтобы протестировать крошечное изменение кода в одном из сервисов. Так что же нам делать?
Прежде чем решать проблему, давайте сделаем шаг назад. Зачем нам нужны другие микросервисы для тестирования крошечного изменения кода в одном из сервисов? Ответ заключается в том, что сервисы взаимодействуют друг с другом для достижения общей цели. И нам нужно убедиться, что изменение кода не нарушит существующую функциональность. На самом деле достаточно убедиться, что изменение кода не повлияет на API сервиса. Если API сервиса не поврежден, то можно с уверенностью предположить, что потребительские микросервисы этого конкретного сервиса будут работать правильно. Так мы можем избежать запуска всего приложения для тестирования изменений в сервисе. Но каким способом будет проверяться целостность API сервиса? Ответ — с помощью тестирования контракта. Давайте рассмотрим простой пример, чтобы лучше это понять.
Простая система предоставления займов
Пример намеренно упрощен, чтобы сосредоточиться на тестировании контрактов. Экосистема работает следующим образом.
Пользователь подает заявку на получение кредита, обращаясь к API шлюза
API шлюза получает фрод-статус по протоколу Http.
Как только фрод-статус получен, он эмитирует событие о предоставлении кредита.
Ожидается, что сервис предоставления займа сможет прослушать это событие и выполнить дальнейшую обработку.
Сервисы Loan Gateway, Fraud Service и Loan Fulfillment разрабатываются и развертываются отдельно как микросервисы. Можно предположить, что для передачи сообщений используется Kafka. Ниже приведена пирамида тестов для этих сервисов, которая выглядит следующим образом
Глядя на пирамиду тестов, можно понять, что модульные и интеграционные тесты для каждого сервиса являются независимыми. Однако функциональный набор является общим, это связано с тем, что нам нужно убедиться, что система в целом работает, когда все микросервисы связаны друг с другом.
Проблема такого подхода заключается в том, что теперь уже нельзя утверждать, будто эти микросервисы являются независимыми. Это обусловлено тем, что для развертывания любого сервиса нужно запустить функциональный набор, который зависит от работы всех остальных сервисов.
Ниже показано, как выглядит CI-пайплайн кредитного шлюза.
Что если мы сможем устранить зависимость от других сервисов? Что если на этапе сборки мы убедимся, что интеграция с другими службами пройдет нормально? Если мы сможем этого добиться, то пайплайн будет выглядеть следующим образом.
Желтые квадратики исчезнут. Это то, что мы получим, применив тестирование контрактов.
Тест контракта проверяет, соблюдает ли сервис контракт с другими сервисами или нет. Контракт — это соглашение между сервисом-потребителем и сервисом-поставщиком. Коммуникационная среда между сервисами может быть синхронной или асинхронной. Обе среды могут быть проверены с помощью тестов контрактов.
Тестирование контракта потребителя сервиса
Как следует из названия, потребитель создает контракт, описывающий его ожидания от службы-поставщика. Служба провайдера выполняет контракт, предоставленный потребителем, и убеждается, что ожидания потребителя оправдались. Если ожидания не удовлетворяются, то провайдер уже не может что-либо изменить.
На этом пока все. В следующем блоге мы рассмотрим, как писать тесты для контрактов потребителей сервиса, используя Pact для синхронного взаимодействия.
Материал подготовлен в рамках курса «Разработчик на Spring Framework». Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем курса — приглашаем на день открытых дверей онлайн. Регистрация здесь.
За всё ответишь! Consumer Driven Contracts глазами разработчика
В этой статье мы расскажем про проблемы, которые решает Consumer Driven Contracts, покажем как это применять на примере Pact с Node.js и Spring Boot. И расскажем про ограничения этого подхода.
Проблематика
Ниже по той же пирамиде находятся другие тесты, в которых мы размениваем достоверность на меньшие головные боли поддержки — с помощью изоляции проверок. Чем гранулярнее, меньше по масштабу проверка, тем меньше зависимость от внешней среды. На самом низу пирамиды находятся unit-тесты. Мы проверяем отдельные функции, классы, мы оперируем уже не столько бизнес-семантикой, сколько конструкциями конкретной реализации. Эти тесты дают быструю обратную связь.
Но как только мы спускаемся ниже по пирамиде, приходится чем-то замещать окружение. Появляются заглушки — как целых сервисов, так и отдельных сущностей языка программирования. Именно с помощью заглушек мы можем тестировать компоненты в изоляции. Но также они уменьшают достоверность проверок. Как убедиться, что заглушка отдаёт правильные данные? Как обеспечивать ее качество?
Решением может быть исчерпывающая документация, которая описывает различные сценарии и возможные состояния компонентов системы. Но любые формулировки все равно оставляют свободу интерпретации. Поэтому хорошая документация — это живой артефакт, который постоянно улучшается по мере понимания командой проблемной области. Как тогда обеспечивать соответствие заглушек документации?
На многих проектах можно наблюдать ситуацию, когда заглушки пишут те же ребята, которые разрабатывали тестируемый артефакт. К примеру, разработчики мобильных приложений делают заглушки для своих тестов сами. В итоге — программисты могут понять документацию по-своему (что совершенно нормально), заглушку делают с неправильным ожидаемым поведением, пишут в соответствии с ним код (с зелеными тестами), а при реальной интеграции сыпятся ошибки.
Более того, документация обычно движется downstream — клиенты используют спеки сервисов (при этом клиентом сервиса может быть и другой сервис). Она не выражает того, как потребители используют данные, какие данные вообще нужны, какие допущения они делают для этих данных. Следствием такого незнания является закон Хайрама.
Хайрам Райт долгое время разрабатывал инструменты общего пользования внутри Google и наблюдал, как самые незначительные изменения могут вызвать поломки у клиентов, которые использовали неявные (недокументированные) особенности его библиотек. Такая скрытая связность усложняет эволюцию API.
Перечисленные проблемы в некоторой степени можно решить с помощью Consumer Driven Contracts. Как и у любого подхода и инструмента, у него есть область применимости и стоимость, которые мы тоже рассмотрим. Реализации этого подхода достигли достаточного уровня зрелости, чтобы пробовать на своих проектах.
Что такое CDC?
Три ключевых элемента:
Контракт фокусируется на том поведении, которое действительно важно потребителю. Делает явными его допущения относительно API.
Главная задача CDC — сблизить понимание поведения API его разработчиками и разработчиками его клиентов. Этот подход хорошо сочетается с BDD, на встречах трёх амиго можно набрасывать заготовки для контракта. В конечном счёте, этот контракт так же служит улучшению коммуникаций; разделению общего понимания проблемной области и реализации решения внутри и между командами.
Рассмотрим применение CDC на примере Pact, одной из его реализации. Допустим, мы делаем web-приложение для участников конференций. В очередной итерации команда разрабатывает отображение расписания выступлений — пока без каких либо историй вроде голосования или заметок, только вывод сетки докладов. Исходники примера лежат здесь.
На встрече трёх четырёх амиго встречаются продакт, тестировщик, разработчики бэкенда и мобильного приложения. Они проговаривают, что
После чего разработчик фронтенда идёт писать код клиента (backend for frontend). Он устанавливает в проекте библиотеку для работы с pact-контрактом:
И начинает писать тест. В нем настраивается локальный сервер заглушек, который будет моделировать работу сервиса с расписаниями докладов:
Контракт представляет собой JSON-файл, в котором описаны сценарии взаимодействия клиента с сервисом. Но описывать его вручную не нужно, так как он формируется из настроек заглушки в коде. Разработчик перед тестом описывает следующее поведение.
Здесь в примере мы указали конкретный ожидаемый запрос к сервису, но pact-js также поддерживает несколько способов определения совпадений.
И наконец, программист пишет тест той части кода, которая использует эту заглушку. В следующем примере мы для простоты вызовем её напрямую.
В реальном проекте это может быть как быстрый unit-тест отдельной функции интерпретации ответа, так и медленный UI-тест отображения полученных от сервиса данных.
Во время прогона теста, pact проверяет, что заглушка получила заданный в тестах запрос. Расхождения можно посмотреть в виде diff в файле pact.log.
Если тест проходит успешно, то формируется контракт в формате JSON. В нём описано ожидаемое поведение API.
Он отдаёт этот контракт разработчику бэкенда. Допустим, API будет на Spring Boot. У Pact есть библиотека pact-jvm-provider-spring, которая умеет работать с MockMVC. Но мы расмотрим на Spring Cloud Contract, который реализует CDC в экосистеме Spring. Он использует свой формат контрактов, но также имеет точку расширения для подключения конвертеров из других форматов. Его родной формат контрактов поддерживается только самим Spring Cloud Contract — в отличие от Pact, у которого есть библиотеки для JVM, Ruby, JS, Go, Python и т.д.
Допустим, в нашем примере разработчик бэкенда использует Gradle для сборки сервиса. Он подключает следующие зависимости:
Из неё по-умолчанию плагин spring-cloud-contract вычитывает контракты. При сборке исполняется gradle-задача generateContractTests, которая формирует в директории build/generated-test-sources следующий тест.
При запуске этого теста мы увидим ошибку:
Поскольку для тестирования мы можем использовать разные инструменты, нужно подсказать плагину, какой именно у нас настроен. Это делается через базовый класс, который будут наследовать сгенерированные из контрактов тесты.
Чтобы этот базовый класс использовался при генерации, нужно донастроить gradle-плагин spring-cloud-contract.
Теперь у нас генерируется такой тест:
Тест успешно запускается, но завершается ошибкой проверки — реализацию сервиса разработчик еще не написал. Но он теперь может это делать, опираясь на контракт. Он может удостовериться, что способен обработать запрос клиента и вернуть ожидаемый ответ.
Разработчик сервиса знает через контракт, что ему нужно сделать, какое поведение реализовать.
Pact можно интегрировать глубже в процесс разработки. Можно развернуть Pact-broker, который агрегирует такие контракты, поддерживает их версионность и может показывать граф зависимостей.
Загрузку нового сгенерированного контракта в брокер можно сделать шагом CI при сборке клиента. А в коде сервера указать динамическую загрузку контракта по URL. Spring Cloud Contract это также поддерживает.
Применимость CDC
Какие ограничения есть у Consumer Driven Contracts?
За использование такого подхода приходится платить дополнительными инструментами вроде pact. Сами по себе контракты — это дополнительный артефакт, ещё одна абстракция, которую нужно аккуратно поддерживать, осознанно применять к ней инженерные практики.
Они не заменяют e2e тесты, так как заглушки всё равно остаются заглушками — моделями реальных компонентов системы, которые может и чуть-чуть, но не соответствуют действительности. Через них не проверить сложные сценарии.
Также CDC не заменяют функциональные тесты API. Они дороже в поддержке, чем Plain Old Unit Tests. Разработчики Pact рекомендуют пользоваться следующей эвристикой — если убрать контракт и это не вызовет ошибки или неправильную интерпретацию со стороны клиента — значит он не нужен. К примеру, не нужно через контракт описывать абсолютно все коды ошибок API, если клиент обрабатывает их одинаково. Другими словами, контракт описывает для сервиса только то, что важно его клиенту. Не больше, но и не меньше.
Слишком большое количество контрактов также усложняет эволюцию API. Каждый дополнительный контракт — это повод для красных тестов. Нужно проектировать CDC таким образом, чтобы каждый fail теста нес в себе полезную смысловую нагрузку, которая перевешивает стоимость его поддержки. К примеру, если в контракте зафиксировать минимальную длину некоторого текстового поля, которая безразлична потребителю (он применяет технику Toleran Reader), то каждое изменение этого минимального значения будет ломать контракт и нервы окружающих. Такую проверку нужно переносить на уровень самого API и реализовывать в зависимости от источника ограничений.
Заключение
CDC улучшает качество продукта за счет явного описания интеграционного поведения. Он помогает разработчикам клиентов и сервисов достичь общего понимания, позволяет разговаривать через код. Но делает это ценой добавления инструментария, введением новых абстракций и дополнительными действиями членов команды.
В то же время, инструменты и фреймворки CDC активно разрабатываются и уже достигли зрелости для пробы на ваших проектах. Тестируйте 🙂
На конференции QualityConf 27-28 мая Андрей Маркелов расскажет про техники тестирования на проде, а Артур Хинельцев — про мониторинг высоконагруженного фронтенда, когда цена даже маленькой ошибки — это десятки тысяч грустных пользователей.
Приходите пообщаться за качество!
Сегодня утром произошел следующий диалог с другом-программистом:
— Знаком с понятием contract testing?
— Как ты работаешь, не зная о такой методологии
А я ведь ни сном, ни духом о подобной методологии. Даже слово какое-то незнакомое. Погуглил маленько в интернете, но информации об этом нуль.
Расскажете или подскажете какую-нибудь книгу, где описывается эта тайна методология?
Я могу предположить, что программист имел в виду подход, который называется design by contract
https://en.wikipedia. ign_by_contract
и который, конечно, является не подходом к тестированию, а подходом к разработке вообще.
В частности, при этом описывается так называемый «программный контракт», который чаще всего представляет собой формальную спецификацию поведения. Некоторые языки (в основном достаточно экзотические) имеют встроенные возможности для описания контрактов, для других языков существуют расширения (см. википедию либо гуглите по словам design by contract и названию нужного языка).
Либо описание контракта может использоваться для проектирования тестов. То есть ставится цель обеспечить полное покрытие этой формальной спецификации, и создаются (вручную или автоматически) тесты, которые достигают этой цели.
Я могу предположить, что программист имел в виду подход, который называется design by contract
https://en.wikipedia. ign_by_contract
и который, конечно, является не подходом к тестированию, а подходом к разработке вообще.
В частности, при этом описывается так называемый «программный контракт», который чаще всего представляет собой формальную спецификацию поведения. Некоторые языки (в основном достаточно экзотические) имеют встроенные возможности для описания контрактов, для других языков существуют расширения (см. википедию либо гуглите по словам design by contract и названию нужного языка).
Либо описание контракта может использоваться для проектирования тестов. То есть ставится цель обеспечить полное покрытие этой формальной спецификации, и создаются (вручную или автоматически) тесты, которые достигают этой цели.
Поинтересовался у друга ссылкой на эту методологию. Прислал следующее: http://martinfowler. ct-introduction
Пока что читаю, но пока не понимаю саму суть этой техники и вообще чем она так хороша в отличие от других «старых» методологий.




