Генерация и поддержка контрактов с помощью ИИ
Updated Jul 2026
От ручного подхода к автоматизированным контрактам
Главный барьер для внедрения Pact -- трудоёмкость ручного написания потребительских тестов. ИИ устраняет этот барьер, анализируя реальный клиентский код обращения к API и автоматически генерируя контракты.
Генерация контрактов с помощью ИИ
Промпт
Analyze this API client code and generate Pact consumer tests for every
external API call it makes.
```typescript
// services/product-client.ts
export class ProductClient {
constructor(private baseUrl: string, private token: string) {}
async getProduct(id: string): Promise<Product> {
const res = await fetch(`${this.baseUrl}/api/v2/products/${id}`, {
headers: { Authorization: `Bearer ${this.token}` }
});
if (!res.ok) throw new ApiError(res.status, await res.text());
return res.json();
}
async searchProducts(query: string, limit = 20): Promise<ProductList> {
const res = await fetch(
`${this.baseUrl}/api/v2/products?q=${encodeURIComponent(query)}&limit=${limit}`,
{ headers: { Authorization: `Bearer ${this.token}` } }
);
if (!res.ok) throw new ApiError(res.status, await res.text());
return res.json();
}
}
Generate Pact consumer tests using @pact-foundation/pact that:
- Define the expected request (method, path, headers, query params)
- Define the expected response shape (using Pact matchers for flexibility)
- Cover both success and error scenarios
- Use Pact matchers (like, eachLike, term) instead of exact values
### Что ИИ обнаруживает из клиентского кода
| Паттерн в клиентском коде | Сгенерированный контракт |
|---------------------------|--------------------------|
| `fetch(\`/api/v2/products/${id}\`)` | Взаимодействие GET /api/v2/products/{id} |
| `headers: { Authorization: \`Bearer ${token}\` }` | Ожидание заголовка авторизации |
| `if (!res.ok) throw new ApiError(...)` | Взаимодействия для сценариев ошибок |
| `res.json()` | Ожидание JSON-тела ответа |
| `query: string, limit = 20` | Ожидания query-параметров |
| `Promise<Product>` | Структура тела ответа из типа Product |
## Роль ИИ в поддержке контрактов
| Задача | Традиционный подход | Подход с ИИ |
|--------|---------------------|-------------|
| Новая функция потребителя | Вручную написать новые тесты Pact | ИИ анализирует клиентский код, генерирует контракт |
| Изменение схемы провайдера | Тесты потребителя падают в CI | ИИ обнаруживает дрейф, предлагает обновление контракта |
| Разрешение конфликтов контрактов | Ручные переговоры между командами | ИИ определяет минимально совместимый контракт |
| Обнаружение пробелов покрытия | Ручной аудит | ИИ сравнивает пути кода клиента с взаимодействиями Pact |
### Обнаружение пробелов покрытия
ИИ может сравнить клиентский код обращения к API с существующими взаимодействиями Pact и найти пропущенное покрытие:
```python
class ContractCoverageAnalyzer:
def __init__(self, llm, client_code: str, pact_file: str):
self.llm = llm
self.client_code = client_code
self.pact = json.load(open(pact_file))
def find_gaps(self) -> list[str]:
"""Find API calls in client code without Pact coverage."""
prompt = f"""
Compare these two artifacts:
1. API CLIENT CODE (all HTTP calls the consumer makes):
{self.client_code}
2. PACT INTERACTIONS (all API calls covered by contracts):
{json.dumps([i['description'] for i in self.pact['interactions']])}
List any API calls in the client code that do NOT have a
corresponding Pact interaction. For each gap, describe:
- The HTTP method and path
- What the client expects in the response
- Why this gap matters (what could break without a contract)
"""
return self.llm.generate(prompt)
Автоматическое обновление контрактов
Когда схема провайдера изменяется, ИИ может предложить минимальное обновление контракта:
class ContractUpdateAdvisor:
def suggest_update(self, old_schema: dict, new_schema: dict, pact: dict) -> str:
"""Suggest contract updates when provider schema changes."""
prompt = f"""
The provider's schema has changed. Analyze the change and determine
if any Pact contracts need updating.
OLD SCHEMA (relevant section):
{json.dumps(old_schema, indent=2)}
NEW SCHEMA (relevant section):
{json.dumps(new_schema, indent=2)}
CURRENT PACT INTERACTIONS:
{json.dumps(pact['interactions'], indent=2)}
For each affected interaction:
1. Describe what changed
2. Whether the change is backward-compatible
3. If not backward-compatible, suggest the minimal contract update
4. Flag if the consumer code likely needs changes too
"""
return self.llm.generate(prompt)
Антипаттерны контрактного тестирования
Антипаттерн 1: Точное сопоставление значений
// BAD: contracts that match exact values
.willRespondWith(200, (builder) => {
builder.jsonBody({
id: "550e8400-e29b-41d4-a716-446655440000", // Exact ID
name: "Blue Widget", // Exact name
price: 29.99, // Exact price
});
})
// GOOD: contracts that match structure and type
.willRespondWith(200, (builder) => {
builder.jsonBody({
id: uuid(), // Any valid UUID
name: like("Blue Widget"), // Any string
price: decimal(29.99), // Any decimal number
});
})
Антипаттерн 2: Тестирование логики провайдера в тестах потребителя
// BAD: consumer test that validates provider business logic
.executeTest(async (mockServer) => {
const product = await client.getProduct('ABC-123');
expect(product.price).toBeLessThan(100); // Business rule validation
expect(product.name.length).toBeLessThan(200); // Schema constraint
})
// GOOD: consumer test that validates consumer behavior
.executeTest(async (mockServer) => {
const product = await client.getProduct('ABC-123');
expect(product.name).toBeDefined(); // Consumer needs the name field
expect(product.price).toBeDefined(); // Consumer needs the price field
})
Антипаттерн 3: Один массивный контракт
// BAD: one test with every field
.willRespondWith(200, builder => {
builder.jsonBody({
id: uuid(), name: like(""), price: decimal(0),
category: like(""), in_stock: like(true),
created_at: like(""), updated_at: like(""),
description: like(""), images: eachLike(""),
tags: eachLike(""), weight: decimal(0),
dimensions: like({}), shipping_info: like({}),
// ... 30 more fields
});
})
// GOOD: one test per consumer use case, only fields the consumer uses
// Use case 1: product listing (needs id, name, price, image)
// Use case 2: product detail (needs all fields)
// Use case 3: cart display (needs id, name, price, in_stock)
Краткий справочник матчеров Pact
| Матчер | Назначение | Пример |
|---|---|---|
like(value) |
Совпадение типа, а не точного значения | like("any string") |
eachLike(example) |
Массив, где каждый элемент совпадает | eachLike({id: uuid()}) |
uuid() |
Валидный формат UUID | uuid() |
decimal(example) |
Десятичное число | decimal(29.99) |
integer(example) |
Целое число | integer(42) |
boolean(example) |
Булево значение | boolean(true) |
term({generate, regex}) |
Совпадение с regex-паттерном | term({generate: "electronics", regex: "electronics|clothing"}) |
datetime(format, example) |
ISO datetime | datetime("yyyy-MM-dd'T'HH:mm:ss", "2026-01-01T00:00:00") |
Ключевой вывод
ИИ трансформирует контрактное тестирование из ручной рутины в автоматизированный конвейер. Анализируя клиентский код, ИИ генерирует точные потребительские контракты, обнаруживает пробелы покрытия и предлагает обновления при изменении схем. Ключ -- использование матчеров Pact для гибкости (а не точных значений) и тестирование только того, от чего потребитель реально зависит.