Что такое Contract Testing?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Contract Testing?
Определение
Contract Testing — это методология тестирования, которая проверяет контракт (договор) между компонентами системы, обычно между consumer (потребителем) и provider (поставщиком) API или сервиса. Тесты контракта убеждают, что обе стороны соблюдают согласованный интерфейс.
Проблема, которую решает Contract Testing
В микросервисной архитектуре возникает проблема:
- Integration тесты дорогие — нужно запускать все сервисы
- Хрупкие тесты — зависят от состояния других сервисов
- Медленные — ждут ответа от реальных сервисов
- Разработчики разных сервисов работают независимо — легко сломать совместимость
- Неясно, какой сервис виноват при падении теста
Без Contract Testing:
┌──────────────┐ ┌──────────────┐
│ Service A │────────→│ Service B │
│ (Consumer) │ │ (Provider) │
└──────────────┘ └──────────────┘
↓ ↓
Full E2E Test Full E2E Test
(дорого, медленно)
Contract Testing решение
С Contract Testing:
┌──────────────┐ ┌──────────────┐
│ Service A │ │ Service B │
│ (Consumer) │ ← Contract → │ (Provider) │
└──────────────┘ (mock) └──────────────┘
↓ ↓
Consumer Tests Provider Tests
(быстро, независимо) (проверка совместимости)
Типы Contract Testing
1. Synchronous Contract Testing (для REST/gRPC)
Тестирование request-response контракта между двумя сервисами.
// Consumer тест: проверяем, что используем правильный API
@ExtendWith(PactConsumerTestExt.class)
public class OrderServiceConsumerTest {
@Pact(consumer = "OrderService", provider = "PaymentService")
public V4Pact createPact(PactBuilder builder) {
return builder
.uponReceiving("a request to process payment")
.path("/api/v1/payments")
.method("POST")
.headers("Content-Type", "application/json")
.body(matchJson(map(
"amount", 100.50,
"currency", "USD",
"orderId", "ORD-123"
)))
.willRespondWith(200)
.body(matchJson(map(
"transactionId", "TXN-456",
"status", "COMPLETED"
)))
.toPact();
}
@Test
void testProcessPayment(MockServer mockServer) throws IOException {
PaymentClient client = new PaymentClient(mockServer.getUrl());
Payment payment = Payment.builder()
.amount(100.50)
.currency("USD")
.orderId("ORD-123")
.build();
PaymentResponse response = client.processPayment(payment);
assertThat(response.getTransactionId()).isEqualTo("TXN-456");
assertThat(response.getStatus()).isEqualTo("COMPLETED");
}
}
// Provider тест: проверяем, что API работает правильно
@ExtendWith(PactVerificationTestExt.class)
public class PaymentServiceProviderTest {
@BeforeEach
void setup() {
// Стартуем PaymentService на определенном порту
PaymentService.start(8080);
}
@TestTemplate
@PactDirectory("pacts")
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}
}
Этот пример использует Pact — популярный фреймворк для Contract Testing.
2. Asynchronous Contract Testing (для Event-Driven)
Для систем с асинхронной коммуникацией (очереди сообщений, Kafka).
// Consumer: слушаем события OrderCreated
@ExtendWith(PactConsumerTestExt.class)
public class InventoryServiceConsumerTest {
@Pact(consumer = "InventoryService", provider = "OrderService")
public V4Pact createEventPact(PactBuilder builder) {
return builder
.uponReceiving("an OrderCreated event")
.aMessageInteraction()
.withContent(matchJson(map(
"eventType", "ORDER_CREATED",
"orderId", "ORD-123",
"items", array(
map("sku", "ITEM-1", "quantity", 5)
),
"timestamp", "2024-03-23T10:00:00Z"
)))
.toPact();
}
@Test
void testOrderCreatedEventProcessing() {
// Получили событие
String eventPayload = "{\"eventType\":\"ORDER_CREATED\", ...}";
// Обрабатываем его
InventoryService service = new InventoryService();
service.processOrderCreatedEvent(eventPayload);
// Проверяем, что инвентарь обновился
assertThat(service.getInventory("ITEM-1")).isEqualTo(95);
}
}
Contract Testing с Pact
Pact — стандартный инструмент для Contract Testing:
import au.com.dius.pact.consumer.dsl.PactBuilder;
import au.com.dius.pact.core.model.V4Pact;
public class UserServiceContractTest {
@Pact(consumer = "UserPortal", provider = "UserAPI")
public V4Pact getUserPact(PactBuilder builder) {
return builder
.uponReceiving("a request to get user by ID")
.path("/api/v1/users/123")
.method("GET")
.willRespondWith(200)
.body(matchJson(map(
"id", 123,
"name", "John Doe",
"email", "john@example.com",
"roles", array("ADMIN", "USER")
)))
.toPact();
}
@Test
@PactTestFor(pactMethod = "getUserPact")
void testGetUserById(MockServer mockServer) throws IOException {
UserClient client = new UserClient(mockServer.getUrl());
User user = client.getUserById(123);
assertThat(user.getId()).isEqualTo(123);
assertThat(user.getName()).isEqualTo("John Doe");
assertThat(user.getRoles()).contains("ADMIN", "USER");
}
}
Процесс Contract Testing
1. Consumer разработчик пишет тесты контракта
↓
2. Тесты запускаются с MockServer
↓
3. Контракт сохраняется в файл (pact файл)
↓
4. Контракт отправляется на Pact Broker (центральный репо контрактов)
↓
5. Provider разработчик скачивает контракт
↓
6. Provider запускает свои тесты против контракта
↓
7. Если тесты прошли → контракт совместим
↓
8. Если нет → нужно обновить API
Преимущества Contract Testing
// 1. Быстрые тесты (mock вместо реального сервиса)
@Test
void fastConsumerTest() {
// Вместо
// PaymentService.start(); // 5 секунд
// Используем
PaymentClient client = new PaymentClient(mockServer);
// Готово мгновенно
}
// 2. Независимая разработка
// Consumer разработчик может тестировать свою логику
// даже если Provider API еще не готов
// 3. Раннее обнаружение несовместимости
// Ошибки в контракте поймаются до merge в main
// 4. Документация
// Pact файлы служат живой документацией интеграции
Consumer-Driven Contracts (CDC)
Специальный подход Contract Testing, где consumer определяет требования:
1. Consumer говорит: "Мне нужен такой API"
2. Пишет consumer-contract тест
3. Provider смотрит на требования consumer
4. Реализует API согласно контракту
Инструменты
- Pact — самый популярный (Java, Go, JS, Ruby, .NET, Python)
- Spring Cloud Contract — интеграция с Spring
- Pacto — альтернатива
- Stubby4j — для создания mock сервисов
Лучшие практики
- Пиши realistic контракты — только то, что реально используется
- Версионируй контракты — отслеживай изменения
- Используй matchers — не захардкодь значения
- Один контракт на одну интеграцию — ясная структура
- Интегрируй в CI/CD — автоматическая проверка
Итоговый вывод
Contract Testing — это essential практика в микросервисной архитектуре. Она позволяет разработчикам разных команд работать независимо и параллельно, при этом гарантируя совместимость интерфейсов. Contract Testing значительно ускоряет разработку и снижает риск интеграции, что критично для больших систем.