Главное за 30 секунд

Привет! В этой статье я расскажу о новом подходе к генерации автотестов для сложных финансовых протоколов. Мы максимально декомпозировали задачу создания тестового покрытия, разбив её на независимые шаги, каждый из которых решает конкретную проблему.

Что вы узнаете:

  • Как разбить сложную задачу автоматизации тестирования на управляемые этапы

  • Какие проблемы возникают на каждом шаге и как их решать

  • Почему важно не пытаться сделать всё идеально с первого раза

  • Как использовать AI для ускорения, но не полагаться на него полностью

В статье подробно описан каждый шаг нашего пути: от первых экспериментов с AI до полноценного тестового покрытия протокола TWIME. Конкретные результаты и метрики вы найдёте в конце статьи.

Контекст задачи

О проекте: Этот эксперимент проводился в отделе RAPID — команде, занимающейся разработкой высокопроизводительных торговых систем и протоколов для финансового рынка.

TWIME (TWIME ASTS) — низкоуровневый транзакционный протокол Московской биржи (MOEX) для фондового и валютного рынков, построенный на базе стандарта FIX Simple Binary Encoding. Протокол используется для обработки заявок, сделок и маркет-данных в режиме реального времени и доступен из зоны колокации, Универсальной схемы и ConnectME. Это критически важная система, где ошибки недопустимы.

Официальная документация: https://ftp.moex.com/pub/TWIME/ASTS/docs/

Особенность задачи: речь идёт не просто о тестировании базовых сценариев, а о всестороннем покрытии всех возможных некорректных сценариев — граничных случаев, ошибок проверки данных, нарушений протокола и аномальных ситуаций. Именно эти сценарии критичны для стабильности биржевой системы.

Экспериментальная задача: исследовать, можно ли с помощью AI ускорить и удешевить создание дополнительных тестов, охватывающих:

  • Валидацию различных типов сообщений

  • Проверку бизнес-логики торговых операций

  • Граничные случаи и обработку ошибок

  • Многопользовательские сценарии

  • Нестандартные ситуации

Наш экспериментальный подход: мы прошли через 3 итеративных шага, где каждый следующий этап строился на опыте предыдущего. На каждом шаге мы анализировали ошибки, добавляли недостающие данные и корректировали подход. Цель эксперимента — проверить, можно ли сократить время и стоимость разработки новых тестов, сохраняя при этом их качество и надёжность.

Шаг 1: Быстрая генерация базового покрытия

Цель первого шага

Быстро создать максимальное количество тестов, покрывающих основные сценарии протокола TWIME. На этом этапе важнее охват, чем качество - мы хотели понять масштаб задачи и структуру будущих тестов.

Архитектура подхода

Прежде чем начать массовую генерацию, мы определили трёхэтапный процесс работы с AI:

Этап 1: Подготовка контекста системы
Первым делом мы создали парсер документации для извлечения структурированной информации из PDF спецификации TWIME. Цель — собрать максимум данных о системе в формате, понятном для языковой модели:

  • Бизнес-правила и требования протокола

  • Описания типов сообщений и их полей

  • Коды ошибок и условия их возникновения

  • Граничные значения и ограничения

# Пример структуры извлечённых данных
extracted_context = {
    "business_rules": [...],      # Правила из спецификации
    "message_types": [...],        # Типы сообщений SBE
    "error_codes": [...],          # Возможные ошибки
    "field_constraints": [...]     # Ограничения на поля
}

Этап 2: Создание генератора тестов
Затем мы предоставили AI эталонный тест — простой, но полностью рабочий пример, демонстрирующий:

  • Как устанавливается соединение с системой

  • Как формируются и отправляются сообщения

  • Как проверяются ответы

  • Какая структура pytest теста ожидается

На основе этого эталона модель сгенерировала генератор автотестов — Python-скрипт, который принимает на вход структурированное описание теста и создаёт готовый pytest файл. Это стало переиспользуемой основой для всей последующей генерации.

Этап 3: Генерация конфигураций тестов
Наконец, используя подготовленный контекст о системе, мы попросили AI создавать YAML-конфигурации для каждого тестового сценария. Эти файлы описывали:

  • Что тестируем (название и категория)

  • Какие сообщения отправлять

  • Какие ответы ожидать

  • Какие проверки делать

Генератор из этапа 2 превращал эти конфигурации в готовые pytest тесты.

Ключевое преимущество: такая архитектура позволила разделить ответственность — AI генерирует высокоуровневые сценарии на основе документации, а стабильный генератор гарантирует единообразный и корректный код тестов.

Исходные данные для генерации

1. PDF спецификация TWIME (официальная документация MOEX)

# Парсер PDF-спецификации
import pdfplumber
import requests

def extract_pdf_rules(pdf_url):
    response = requests.get(pdf_url)
    with pdfplumber.open(BytesIO(response.content)) as pdf:
        text = ""
        for page in pdf.pages:
            text += page.extract_text()
    
    # Ищем правила с ключевыми словами
    rules = re.findall(r'(?i)(must|shall|should|required)[^.]+\.', text)
    return rules

Парсер извлекает из PDF-документации все бизнес-правила протокола, ищя ключевые слова (must, shall, should, required), которыми обычно помечаются обязательные требования в технических спецификациях. Эти правила затем становятся основой для генерации тестовых сценариев.

Извлечение правил из документации можно выполнять вручную — просто прочитать спецификацию и составить список требований. Однако для дальнейшей автоматизации всего процесса мы экспериментировали с автоматизированным парсингом, чтобы в будущем система могла самостоятельно обрабатывать обновления документации без участия человека.

2. Эталонный тест - рабочий пример для понимания структуры

def test_simple_order():
    with gen.managed_session() as session:
        session.send(NewOrderSingle(
            clordid=generate_clordid(),
            price=6714.0,
            qty=100,
            side='Buy'
        ))
        response = session.recv(1, timeout=5)
        assert response[0].message_name == "ExecutionReport"

3. Схема SBE сообщений (XML с описанием всех полей и типов)

Промпт для AI (первая итерация)

Контекст: Протокол TWIME для биржевой торговли. Нужно создать pytest тесты.

Входные данные:
1. Список бизнес-правил из спецификации
2. Пример рабочего теста
3. Схема сообщений SBE

Задача: Сгенерировать YAML конфигурации для автоматической генерации тестов.

Для каждого правила создай:
- Название теста
- Категорию (error, validation, performance, etc.)
- Тестовый сценарий
- Ожидаемый результат
- Параметры для генерации

Пример вывода:
```yaml
test_invalid_market:
  category: error_codes
  description: "Проверка ошибки при невалидном рынке"
  test_params:
    invalid_field: "market"
    invalid_value: "INVALID_MKT"
    expected_error: 71
  steps:
    - send: NewOrderSingle
    - expect: BusinessMessageReject
    - verify: error_code == 71

### Минимальный генератор YAML → pytest

```python
# Генератор тестов из YAML конфигураций
from jinja2 import Template
import yaml

# Простой шаблон теста
TEST_TEMPLATE = """
def test_{{ test_name }}(order_session):
    \"\"\"{{ description }}\"\"\"
    session = order_session
    
    # Отправляем сообщение с невалидным параметром
    session.send(NewOrderSingle(
        clordid=generate_clordid(),
        {{ invalid_field }}="{{ invalid_value }}",
        **default_params()
    ))
    
    # Ожидаем отклонение
    response = session.recv(1, timeout=5)
    assert response[0].message_name == "BusinessMessageReject"
    assert response[0].value['RefMsgType'] == {{ expected_error }}
"""

def generate_test(yaml_config):
    """Превращает YAML в pytest за секунды"""
    template = Template(TEST_TEMPLATE)
    
    with open(yaml_config) as f:
        config = yaml.safe_load(f)
    
    for test_name, params in config.items():
        test_code = template.render(
            test_name=test_name,
            description=params['description'],
            invalid_field=params['test_params']['invalid_field'],
            invalid_value=params['test_params']['invalid_value'],
            expected_error=params['test_params']['expected_error']
        )
        
        # Сохраняем тест
        with open(f"output/test_{test_name}.py", "w") as f:
            f.write(test_code)

9 категорий генерации и их данные

На первом шаге мы сгенерировали тесты для 9 основных категорий:

1. Error Codes Tests (57 тестов)

  • Входные данные: Таблица кодов ошибок из спецификации

  • YAML пример:

error_invalid_symbol:
  category: error_codes
  error_code: 71
  invalid_field: symbol
  invalid_value: "INVALID_SYM"

2. Specification Tests (20 тестов)

  • Входные данные: Бизнес-правила из PDF

  • YAML пример:

spec_session_required:
  rule: "Session must be established before orders"
  test_type: negative
  scenario: "Send order without session"

3. SBE Tests (104 теста)

  • Входные данные: XML схема SBE

  • YAML пример:

sbe_message_header:
  message_type: NewOrderSingle
  required_fields: [ClOrdID, Price, OrderQty]
  encoding: SBE_1_0

4. Performance Tests (10 тестов)

  • Входные данные: SLA требования

  • YAML пример:

perf_latency_test:
  message_count: 1000
  max_latency_ms: 10
  throughput_target: 1000

5. Edge Cases Tests (14 тестов)

  • Входные данные: Граничные значения из схемы

  • YAML пример:

edge_max_price:
  field: price
  value: 999999999.9999
  expected: accepted_or_rejected

6. Integration Tests (18 тестов)

  • Входные данные: Сценарии из документации

  • YAML пример:

integration_order_lifecycle:
  steps: [create, modify, cancel]
  users: 2
  expected_flow: complete

7. Positive Tests (35 тестов)

  • Входные данные: Happy path сценарии

  • YAML пример:

positive_simple_buy:
  side: Buy
  qty: 100
  price: 6714.0
  expected: filled

8. Security Tests (25 тестов)

  • Входные данные: Требования безопасности

  • YAML пример:

security_auth_required:
  test: send_without_auth
  expected: connection_rejected

9. Market Data Tests (18 тестов)

  • Входные данные: Сценарии orderbook

  • YAML пример:

market_data_bbo:
  test_type: best_bid_offer
  bid: 6714.0
  ask: 6716.0
  expected_spread: 2.0

Результат первого шага

Сгенерировано: 307 тестов в 9 категориях
Запустилось с первого раза: 8 тестов из 154 попыток
Поднялось после исправлений и оптимизации: 39 рабочих тестов
Основная проблема: 146 ImportError

Распределение по категориям после первого шага:

  • Edge тесты: 7 (неверные краевые значения)

  • Market Data: 2 (попытки матчинга с одного пользователя)

  • Негативные: 13

  • Позитивные: 2

  • Performance: 2

  • SBE: 13

  • Остальные категории полностью отсеяны

collected 154 items / 146 errors
ImportError: cannot import name 'MarketDataRequest' from protocol module

Проблемы первого шага

1. AI галлюцинировал несуществующие классы

# AI генерировал несуществующие классы:
from protocol import MarketDataRequest, OrderStatusRequest, QuoteRequest

# Реально существовали:
from protocol import NewOrderSingle, OrderCancelRequest, OrderReplaceRequest

2. Неправильные имена полей

# AI использовал поля из FIX протокола:
order_params = {
    "clordid": "123",
    "handlinst": "1",      # ← нет в TWIME
    "timeInForce": "0",    # ← называется tif
    "orderType": "Limit"   # ← называется ord_type
}

3. Текстовые значения вместо числовых

# AI генерировал:
assert response.value['ExecType'] == "New"      # ← текст
assert response.value['OrdStatus'] == "Filled"  # ← текст

# Нужно было (из SBE схемы):
assert response.value['ExecType'] == "0"   # New = 0
assert response.value['OrdStatus'] == "2"  # Filled = 2

Выводы после первого шага

Интересное наблюдение: языковым моделям легко даётся генерация негативных сценариев (что может пойти не так), но без специфичного контекста системы они плохо справляются с позитивными и интеграционными тестами, которые требуют глубокого понимания бизнес-логики.

Что получилось:

  • Быстро покрыли все категории тестов

  • Поняли структуру и паттерны тестирования

  • Создали базовую архитектуру генерации

Что не сработало:

  • AI не знает специфику конкретного протокола

  • Нельзя полагаться на LLM в деталях реализации

  • Без валидации генерация бесполезна

Главный вывод: нужна валидация на основе реального кода и схемы протокола.

Шаг 2: Добавление валидации и реальных данных

Цель второго шага

Исправить массовые ошибки первого шага, добавив валидацию полей и используя реальные данные из кодовой базы.

Дополнительные данные, которые мы добавили

1. Реальная схема сообщений из кода

# Извлекли из кодовой базы реальные классы и поля
VALID_MESSAGES = {
    'NewOrderSingle': ['clordid', 'price', 'qty', 'side', 'ord_type', 'tif'],
    'OrderCancelRequest': ['clordid', 'origclordid'],
    'OrderReplaceRequest': ['clordid', 'origclordid', 'price', 'qty']
}

2. Таблица enum значений из types.txt

<!-- Извлекли из SBE схемы -->
<enum name="ExecType">
    <validValue name="New">0</validValue>
    <validValue name="PartiallyFilled">1</validValue>
    <validValue name="Filled">2</validValue>
    <validValue name="Canceled">4</validValue>
</enum>

3. Документация по валидации заявок (28 сценариев)

  • Проверка идентификаторов инструментов

  • Валидация направления сделок (BUY/SELL)

  • Проверка кодов расчетов

  • Валидация количества лотов и цены

Важно: Для получения документации по валидации мы обратились к бизнес-аналитикам, которые помогли определить нужные разделы спецификации и ключевые сценарии проверок. Это позволило сосредоточиться на реально важных критичных проверках, а не генерировать избыточные тесты.

Новый валидатор полей

# Валидатор полей на основе реальной схемы
def validate_message_fields(message_type: str, fields: dict):
    """Проверяет существование полей перед генерацией"""
    if message_type not in VALID_MESSAGES:
        raise ValueError(f"Unknown message type: {message_type}")
    
    valid_fields = VALID_MESSAGES[message_type]
    invalid = set(fields.keys()) - set(valid_fields)
    
    if invalid:
        raise ValueError(f"Invalid fields {invalid} for {message_type}")
    
    return True

def fix_field_names(fields: dict, message_type: str):
    """Исправляет имена полей на правильные"""
    mapping = {
        'timeInForce': 'tif',
        'orderType': 'ord_type',
        'orderQty': 'qty',
        'clOrdID': 'clordid'
    }
    
    fixed = {}
    for key, value in fields.items():
        fixed_key = mapping.get(key, key)
        fixed[fixed_key] = value
    
    return fixed

Критически важное решение: Мы намеренно использовали детерминированный скрипт для исправления ошибок, а не полагались на языковую модель. Всегда существует риск галлюцинаций AI — модель может «придумать» несуществующие поля или значения. Для критичных задач, таких как исправление import'ов и валидация структуры кода, программный подход гарантирует корректность на 100%.

Система валидации заявок

Автоматическая генерация тестов осуществлялась с использованием Jinja2-шаблонов, что позволило создать большое количество тестов за считанные минуты. Каждый тест включает в себя:

  • Необходимые импорты для работы с pytest, time, конфигурациями и сообщениями

  • Импорт session из тестовых фикстур

  • Структуру тестовой функции с подробными комментариями

  • Логику отправки сообщения NewOrderSingle с заданными параметрами

  • Проверку получения ответа BusinessMessageReject от сервера

  • Проверку соответствия кода ошибки ожидаемому значению

Этот подход значительно ускорил процесс создания тестов и обеспечил их последовательность и воспроизводимость.

Этот шаг включал в себя:

  1. Анализ документации по валидации заявок, содержащей 28 ключевых сценариев проверки корректности входящих сообщений

  2. Подготовку структурированных данных в формате JSON, содержащую информацию о каждом сценарии валидации

  3. Создание Jinja2-шаблона для автоматической генерации тестов

  4. Генерацию отдельных Python-файлов для каждого из 26 реализуемых сценариев валидации

  5. Анализ и правку тестов, устранение проблем с форматами полей и дублирующих тестов

  6. Проведение анализа сценариев и удаление тех, которые невозможно реализовать в текущей тестовой системе

  7. Проверку на дублирование и удаление дублирующих тестов

  8. Тщательную проверку и отредактирование всех сценариев для обеспечения корректности и соответствия требованиям валидации протокола TWIME

В результате этого шага мы получили систему, которая позволяет автоматически генерировать тесты для проверки различных сценариев валидации заявок, включая проверку идентификаторов инструментов, направления сделок, кодов расчетов, количества лотов, цены, типов заявок и других параметров.

Система валидации заявок охватывает следующие ключевые сценарии:

  • Проверка корректности идентификаторов инструментов

  • Валидация направления сделок (BUY/SELL)

  • Проверка кодов расчетов

  • Валидация количества лотов и цены

  • Проверка типов заявок

  • Валидация специальных флагов заявок (по одной цене, немедленно или отклонить)

  • Проверка ограничений по объему и количеству лотов

  • Валидацию соотношения видимой и скрытой части заявки

  • Проверку соответствия цен минимальным шагам цены инструмента

  • Условия применения специальных признаков заявок (например, "снять остаток" вне темного пула)

Все 26 сценариев валидации заявок были реализованы в виде отдельных Python-файлов. Каждый файл содержит:

  • Полный импорт необходимых библиотек и модулей

  • Функцию теста с описанием сценария и шагов теста

  • Отправку сообщения NewOrderSingle с конкретным некорректным параметром

  • Проверку получения ответа BusinessMessageReject от сервера

  • Проверку соответствия кода ошибки ожидаемому значению

  • Комментарии к коду, объясняющие для AI логику управления ClOrdID и очистки заявок

Важная деталь: Комментарии в коде эталонных тестов служат не только документацией для разработчиков, но и подсказками для языковой модели при последующей генерации. Хорошо прокомментированный пример помогает AI понять паттерны и воспроизвести их корректно.

Эти сценарии были тщательно проверены и отредактированы для обеспечения корректности и соответствия требованиям валидации протокола TWIME.

На этом этапе также были проведены корректировки и оптимизации:

  • Удалены тесты, проверяющие невалидные форматы параметров, поскольку в рамках тестовой системы форматы полей строго определены при сериализации сообщения

  • Удалены дублирующие тесты, которые повторяли уже существующие проверки

  • Остались только корректные и реализуемые сценарии валидации заявок

Результаты второго шага

До исправлений: 39 рабочих тестов после отсеивания
После валидации и параметризации: 54 рабочих теста
Новые тесты валидации: 26 сценариев из документации

Распределение по категориям после второго шага:

  • Edge тесты: 5 (сокращены с 7, убраны неверные краевые значения)

  • Market Data: 1 (сокращены с 2, убрали попытки матчинга с одного пользователя)

  • Негативные: 29 (выросли с 13 благодаря параметризации)

  • Позитивные: 4 (выросли с 2)

  • Performance: 2 (без изменений)

  • SBE: 13 (без изменений)

Что исправили:

  • Все импорты теперь используют реальные классы

  • Поля соответствуют схеме протокола

  • Enum значения взяты из SBE схемы

  • Добавлена проверка перед генерацией

  • Ключевая роль параметризации: позволила увеличить покрытие негативных тестов с 13 до 29, используя один тест-шаблон для множества сценариев

Оставшаяся проблема: многопользовательские и сложные интеграционные тесты всё ещё не работали.

Шаг 3: Решение проблемы сложных тестов

Проблема, которая осталась

После второго шага у нас работали простые тесты, но сложные многопользовательские сценарии падали. Нужны были тесты для:

  • Одновременной работы нескольких трейдеров

  • FIFO приоритета заявок

  • Частичного исполнения

  • Различных типов заявок:

    • IoC (Immediate or Cancel) — заявка исполняется немедленно на доступный объём, остаток автоматически отменяется

    • FoK (Fill or Kill) — заявка исполняется полностью или отменяется целиком, частичное исполнение недопустимо

    • Passive-Only — заявка гарантированно становится мейкером (добавляется в стакан), но никогда не исполняется как тейкер

Промпт для многопользовательских тестов

Контекст: Нужны многопользовательские тесты для TWIME.
Ограничения системы: максимум 2 пользователя (userid, userid2).

Создай тесты для следующих сценариев:
1. Непересекающиеся заявки (bid < ask)
2. Полное исполнение (bid = ask, одинаковый объем)
3. Частичное исполнение (bid = ask, разный объем)
4. FIFO приоритет на одной цене
5. IoC заявки (Immediate or Cancel)
6. FoK заявки (Fill or Kill)
7. Market заявки
8. Passive-Only заявки
9. Модификация заявок
10. Отмена заявок

Требования:
- Максимум 2 пользователя
- Подробное логирование каждого шага
- Использовать числовые enum значения
- Никаких утилит или абстракций

Список предложенных моделью тестов (24 теста)

AI предложил следующую структуру тестов:

Базовые операции:
├── test_01_non_crossing_orders       # Непересекающиеся заявки
├── test_02_full_matching             # Полное исполнение
└── test_03_partial_matching          # Частичное исполнение

Типы заявок:
├── test_04_ioc_order                 # Immediate or Cancel
├── test_05_fok_order                 # Fill or Kill
├── test_06_market_order              # Рыночные заявки
├── test_07_order_modification        # Модификация
└── test_08_post_only                 # Passive-Only

Управление заявками:
├── test_09_order_cancellation        # Отмена
├── test_10_time_priority_fifo        # FIFO приоритет
└── test_11_price_priority            # Ценовой приоритет

Сложные сценарии:
├── test_12_mixed_order_types         # Смешанные типы
├── test_13_order_replace             # Замещение
├── test_14_multiple_price_levels     # Многоуровневый стакан
├── test_15_order_rejection           # Отклонения
└── test_16_stress_high_volume        # Стресс-тесты

Продвинутые:
├── test_17_bid_ask_spread            # Спреды
├── test_18_order_book_depth          # Глубина стакана
├── test_19_rapid_order_sequence      # Быстрые последовательности
└── test_20_edge_case_scenarios       # Граничные случаи

Интеграционные:
├── test_21_session_management        # Управление сессиями
├── test_22_order_lifecycle           # Жизненный цикл заявки
├── test_23_market_data_consistency   # Консистентность данных
└── test_24_comprehensive_integration # Комплексная интеграция

Архитектура эталонного теста (без абстракций)

def test_01_non_crossing_orders():
    """Тест непересекающихся заявок от двух пользователей"""
    _log.info("Starting Test 01: Non-crossing orders")
    
    gen = Generator()
    
    # Только 2 пользователя - больше система не поддерживает!
    with gen.managed_session() as session1:
        with gen.managed_session(userid=defaults.userid2, 
                                password=defaults.password2) as session2:
            
            # User1: Buy @ 6714
            clordid1 = int(time.time() * 1000000)
            _log.info(f"User1 placing Buy: ClOrdID={clordid1}, qty=100 @ 6714")
            
            session1.send(NewOrderSingle(
                clordid=clordid1,
                account=defaults.trdaccid,
                clientcode=defaults.client_code1,
                board=defaults.board,
                symbol=defaults.symbol,
                side='Buy',
                qty=100,
                price=6714.0,
                ord_type='Limit',
                tif='Day'
            ))
            
            # Встроенное ожидание - никаких утилит!
            messages1 = []
            start_time = time.time()
            while len(messages1) < 1 and (time.time() - start_time) < 5:
                qrecv = session1.recv(1, 1)
                if qrecv:
                    session1.format_all(qrecv)
                    messages1.extend(qrecv)
            
            assert len(messages1) == 1
            m1 = messages1[0]
            assert m1.message_name == "ExecutionReport"
            assert m1.value['ExecType'] == "0"  # New (числовое!)
            assert m1.value['OrdStatus'] == "0"  # New
            
            _log.info(f"User1 order placed: ExecType=0 (New)")
            
            # User2: Sell @ 6718 (не пересекается)
            clordid2 = int(time.time() * 1000000) + 1
            _log.info(f"User2 placing Sell: ClOrdID={clordid2}, qty=100 @ 6718")
            
            session2.send(NewOrderSingle(
                clordid=clordid2,
                account=defaults.trdaccid2,
                clientcode=defaults.client_code2,
                board=defaults.board,
                symbol=defaults.symbol,
                side='Sell',
                qty=100,
                price=6718.0,
                ord_type='Limit',
                tif='Day'
            ))
            
            messages2 = []
            start_time = time.time()
            while len(messages2) < 1 and (time.time() - start_time) < 5:
                qrecv = session2.recv(1, 1)
                if qrecv:
                    session2.format_all(qrecv)
                    messages2.extend(qrecv)
            
            assert len(messages2) == 1
            m2 = messages2[0]
            assert m2.value['ExecType'] == "0"  # New
            assert m2.value['OrdStatus'] == "0"  # New
            
            _log.info(f"User2 order placed: ExecType=0 (New)")
            _log.info(f"Final: Bid=6714, Ask=6718, Spread=4")
            _log.info("Test 01 PASSED: Orders don't cross")

Результат третьего шага

Сгенерировано: 38 тестов (первая попытка)
Проблема: система поддерживает только 2 пользователей, не 3
Финальный результат: 55 интеграционных тестов

Эволюция архитектуры третьего шага:

  1. Первая попытка: AI создал отдельный класс с валидирующими функциями

  2. Проблема: логи были нечитаемые из-за абстракций

  3. Упрощение: убрали класс, сделали подход без абстракций

  4. Возврат к решению: вернулись к структурированному подходу, но с параметризацией

  5. Финальное решение: добавили параметризацию тестов для покрытия различных сценариев

Метрики улучшения:

  • Время отладки: с ~30 мин до ~5 мин на тест

  • Читаемость логов: с 3/10 до 9/10

  • Работоспособность: 100% (все 55 тестов запускаются)

  • Покрытие: интеграционные тесты покрывают все основные сценарии протокола

Итоговые результаты

Метрики по шагам

Шаг

Сгенерировано

Работает

Проблема

Решение

1

307 тестов

39 после отсеивания

ImportError, галлюцинации

Нужна валидация

2

+26 тестов

54 теста

Параметризация и очистка

Реальные данные + параметризация

3

+55 тестов

55 (100%)

Архитектура, читаемость логов

Итеративное улучшение архитектуры

Итого: 109 рабочих тестов из изначально сгенерированных 307

Выводы и рекомендации

Что сработало хорошо

Итеративный подход с анализом ошибок

  • Каждый шаг строился на опыте предыдущего

  • Анализ ошибок давал понимание недостающих данных

  • Постепенное улучшение качества от 2% до 100%

Отказ от абстракций для сложных тестов

  • Читаемость и отладка важнее переиспользования

  • Явный код с подробными логами экономит время

  • Простота побеждает элегантность в тестировании

Валидация на основе реального кода

  • Извлечение схемы из существующих классов

  • Проверка полей перед генерацией

  • Использование числовых enum из SBE

Чего стоит избегать

Слепое доверие AI без валидации

  • LLM галлюцинирует классы и поля (95% ошибок на шаге 1)

  • AI хорош для структуры, но не для деталей реализации

  • Всегда нужна проверка на реальном коде

Переусложнение архитектуры тестов

  • Утилиты делают логи нечитаемыми

  • Абстракции скрывают важные детали

  • DRY не всегда применим к тестам

Игнорирование системных ограничений

  • Проверяйте реальные возможности (2 пользователя, не 3)

  • Не полагайтесь на документацию на 100%

  • Тестируйте на реальном окружении

Рекомендации для похожих проектов

1. Начните с прототипа

# Сгенерируйте 10 тестов, не 300
# Отладьте процесс на малом объёме
# Масштабируйте после успеха

2. Создайте валидацию до генерации

# Извлеките реальную схему
VALID_MESSAGES = extract_from_code()
# Проверяйте каждое поле
validate_before_generate(scenario)
# Автоматизируйте проверки в CI

3. Документируйте каждый шаг

- Сохраняйте промпты и шаблоны
- Фиксируйте проблемы и решения  
- Делитесь опытом с командой

Контроль обратной совместимости

После завершения всех трёх шагов мы внедрили автоматическую систему контроля регрессий. Все сгенерированные тесты были интегрированы в CI/CD пайплайн и запускаются автоматически при любых изменениях в коде протокола или его схемах. Это гарантирует, что новые изменения не нарушат существующую функциональность, а тестовое покрытие остаётся актуальным и защищает от регрессий при выпуске новых версий.

Финальный результат

109 работающих тестов за 1 неделю:

  • 54 базовых теста из спецификации (после отсеивания и параметризации)

  • 26 валидационных тестов

  • 55 интеграционных тестов (с параметризацией)

Результаты эксперимента:

  • Ускорение: 7-8 недель экономии времени (против 2-3 месяцев традиционной разработки)

  • Качество: 100% трассируемость к спецификации, единообразная структура

  • Интеграция: автоматический контроль регрессий в CI/CD

Вывод: эксперимент показал, что AI-assisted подход может значительно ускорить и удешевить процесс создания дополнительных тестов для сложных протоколов, при этом сохраняя высокое качество и надёжность. Главное — не пытаться решить всё сразу, а двигаться итеративно, учась на каждом шаге и валидируя результаты.

Бизнес-ценность решения

Главная цель проекта была достигнута — расширить существующее тестовое покрытие за минимальную стоимость, чтобы научиться вылавливать краевые сценарии и ускорить вывод новых релизов в продакшн. Вместо 2-3 месяцев ручного написания тестов мы получили рабочее решение за неделю.

О выборе языковых моделей

Важное практическое наблюдение: для этой работы не требуются самые большие и дорогие модели. При правильной декомпозиции задачи мы успешно справлялись с моделями среднего размера:

  • Qwen Coder 3 (30B параметров) — основная рабочая модель для генерации тестов

  • Devstral (32B параметров) — альтернативная модель

Ключевой фактор успеха — не размер модели, а качественная декомпозиция задачи. Когда вы разбиваете сложную проблему на простые шаги, предоставляете чёткие примеры и валидируете каждый этап, даже относительно компактные модели показывают отличные результаты. Это делает AI-assisted разработку доступной без необходимости использовать дорогостоящие API больших моделей или мощное железо для локального запуска.

Практический совет: начните с доступных вам моделей среднего размера и фокусируйтесь на улучшении промптов и декомпозиции задач, прежде чем переходить к более крупным моделям.

Перспективы развития

В потенциале весь описанный процесс можно полностью автоматизировать:

  1. Автоматический парсинг новых версий спецификации при их обновлении

  2. Инкрементальная генерация тестов только для изменённых разделов протокола

  3. CI/CD интеграция с автоматическим запуском сгенерированных тестов

  4. Самообучение системы на основе найденных багов — превращение их в новые тестовые сценарии

Такая система позволит поддерживать актуальность тестового покрытия без ручного вмешательства, автоматически адаптируясь к эволюции протокола.

Автоматизация самой автоматизации

Важно отметить, что большую часть работы с промптами, документацией и ручными операциями можно автоматизировать. В нашем проекте мы выполняли многие шаги вручную — копировали промпты, парсили документацию, исправляли ошибки. Однако современная экосистема AI-инструментов предлагает готовые решения для таких задач.

MCP (Model Context Protocol) и аналогичные инструменты позволяют:

  • Автоматически предоставлять контекст из документации и кодовой базы

  • Создавать переиспользуемые инструменты для работы с кодом

  • Строить пайплайны генерации без ручного копирования промптов

  • Интегрировать валидацию и проверки прямо в процесс генерации

Современные подходы к автоматизации:

  • AI-агенты с доступом к файловой системе и инструментам разработки

  • RAG-системы для автоматического извлечения релевантного контекста из документации

  • Orchestration frameworks (LangChain, CrewAI) для построения сложных пайплайнов

  • Code analysis tools с AI-интеграцией для автоматической валидации

  • CI/CD интеграции с триггерами на обновление спецификаций

Область AI-assisted development стремительно развивается. То, что год назад требовало ручной работы, сегодня можно автоматизировать готовыми инструментами. Поэтому даже описанный нами процесс — это лишь отправная точка. В будущем весь цикл от обновления документации до генерации и валидации тестов может происходить полностью автоматически, требуя вмешательства человека только для финального ревью и принятия решений.

Комментарии (2)


  1. RodionGork
    14.10.2025 07:18

    Да с тестами проблема не в том чтобы их напедалить, а в том чтобы в этой порнографии спустя месяц, два, год можно было понять к чему это напедалено, что и как проверяется.

    Я б избегал пользоваться услугами финансовой биржи где к созданию тестов относятся так, гм, философски :)))