Modern QA2026Генерация и поддержка контрактов с помощью ИИ
Join

Course04 API & Contract Testing with AI

Cutting-edge · Chapter 04

Генерация и поддержка контрактов с помощью ИИ

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:

  1. Define the expected request (method, path, headers, query params)
  2. Define the expected response shape (using Pact matchers for flexibility)
  3. Cover both success and error scenarios
  4. 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 для гибкости (а не точных значений) и тестирование только того, от чего потребитель реально зависит.