Давайте сразу перейдем к сути. Когда микросервисы взаимодействуют друг с другом, они становятся как два разработчика, которые договорились: "Я тебе отправлю файл в формате JSON с такими полями!". В контрактном тестировании этот "договор" называется контрактом. Идея состоит в том, что взаимодействия между сервисами проверяются на соответствие заранее заданным условиям, чтобы убедиться, что никто случайно не нарушил "договор".
Пример: Представьте, что вы работаете над микросервисом "Заказчик", который делает HTTP-запросы к микросервису "Продукты". "Продукты" предоставляет в ответ JSON с информацией о товаре. Вы можете написать контрактное тестирование, чтобы гарантировать, что "Продукты" всегда возвращает корректный JSON, соответствующий ожиданиям "Заказчика".
Почему это важно?
В микросервисной архитектуре, где каждый сервис независим, но взаимодействует с другими, обновление одной части системы может легко сломать другую. Например, если команда разработчиков сервиса "Продукты" внезапно изменит структуру JSON-ответа, то сервис "Заказчик" начнет падать с ошибками. Контрактное тестирование помогает избежать таких ситуаций:
- Оно гарантирует стабильность взаимодействий между сервисами.
- Оно экономит время, так как проблемы с интеграцией находят на ранних этапах разработки.
- Оно уменьшает число ручных проверок, так как взаимодействия тестируются автоматически.
Различие между интеграционным и контрактным тестированием
Интеграционные тесты проверяют взаимодействие нескольких компонентов, а контрактные тесты фокусируются на точном соответствии ожидаемых входных и выходных данных между двумя сервисами. Контрактное тестирование полезно тем, что позволяет тестировать взаимодействие в изолированном окружении, не поднимая оба сервиса.
Основы работы с Pact
Pact — это популярный инструмент для контрактного тестирования. Он служит посредником между двумя сторонами:
- Потребителем (consumer) — сервисом, который отправляет запросы.
- Провайдером (provider) — сервисом, который отвечает на запросы.
Pact позволяет создавать и проверять контракты между этими сторонами.
Основной принцип работы Pact
- Создание контракта: потребитель создает контракт, описывающий, какие запросы он отправляет и какие ответы ожидает от провайдера.
- Проверка контракта: провайдер проверяет контракт, чтобы гарантировать, что он может правильно отвечать на запросы потребителя.
Пример потока работы:
- Сервис "Заказчик" генерирует контракт, где указано:
- Я отправлю GET-запрос на
/products/1 - Я ожидаю в ответе JSON с полями
id,name,price
- Я отправлю GET-запрос на
- Этот контракт передается сервису "Продукты".
- Сервис "Продукты" использует Pact для проверки, что он может отправить такой JSON.
Пример использования Pact для микросервисов
Перейдем к практике. Представим, что у нас есть два микросервиса:
- Consumer (потребитель): сервис "Заказчик", который запрашивает информацию о товарах.
- Provider (провайдер): сервис "Продукты", который возвращает информацию о товарах.
Шаг 1: Создание контракта на стороне потребителя
Мы начнем с "Заказчика". В этом сервисе мы описываем, какой запрос и ответ ожидаем от "Продуктов".
Перед началом работы добавим библиотеку Pact в наш build.gradle:
dependencies {
testImplementation 'au.com.dius.pact.consumer:junit5:4.5.7'
}
Написание теста с использованием Pact
@PactTestFor(providerName = "ProductService")
public class ProductConsumerContractTest {
@Pact(consumer = "CustomerService")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("Product with ID 1 exists")
.uponReceiving("A request for product with ID 1")
.path("/products/1")
.method("GET")
.willRespondWith()
.status(200)
.body("{\"id\": 1, \"name\": \"Laptop\", \"price\": 1200.00}")
.toPact();
}
@Test
@PactTestFor(pactMethod = "createPact")
public void testConsumerBehaviour(MockServer mockServer) {
// Используем mockServer для эмуляции API "Продуктов"
String response = new RestTemplate().getForObject(mockServer.getUrl() + "/products/1", String.class);
// Проверяем, что ответ соответствует ожиданиям
assertEquals("{\"id\": 1, \"name\": \"Laptop\", \"price\": 1200.00}", response);
}
}
Объяснение:
- Мы создаем контракт с помощью Pact.
- В контракте указано, что потребитель (CustomerService) делает
GET-запрос на/products/1и ожидает определенный JSON-ответ. - Pact поднимает
MockServerдля тестирования поведения потребителя.
Шаг 2: Проверка контракта на стороне провайдера
Теперь передадим контракт в сервис "Продукты" и проверим, что он соответствует ожиданиям.
Добавим Pact в провайдерский сервис:
dependencies {
testImplementation 'au.com.dius.pact.provider:junit5:4.5.7'
}
Проверка контракта
@Provider("ProductService")
@PactBroker(host = "localhost", port = "9292") // Брокер Pact для хранения контрактов
public class ProductProviderContractTest {
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
public void validatePacts(PactVerificationContext context) {
context.verifyInteraction();
}
@State("Product with ID 1 exists")
public void productExists() {
// Настраиваем тестовую базу данных или моки для состояния "Продукт с ID 1 существует"
}
}
Объяснение:
- Мы используем Pact для проверки контракта.
- В методе
productExistsзадаем начальное состояние (например, добавляем запись в базу). - Pact автоматически проверяет, что сервис "Продукты" соответствует контракту.
Преимущества использования Pact
- Раннее нахождение проблем: проблемы интеграции между потребителем и провайдером сразу становятся заметны.
- Изоляция тестов: потребитель и провайдер тестируются независимо.
- Документация взаимодействий: контракты служат авто-документацией API.
Заключение темы
Контрактное тестирование — это супергерой микросервисных взаимодействий. Инструмент, подобный Pact, помогает вашим командам спать спокойно, зная, что обновления одного сервиса не разрушат другой. На следующих лекциях мы углубимся в практическое использование Pact и напишем свои первые контрактные тесты для взаимодействия между микросервисами.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ