Главное за 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 от сервера
-
Проверку соответствия кода ошибки ожидаемому значению
Этот подход значительно ускорил процесс создания тестов и обеспечил их последовательность и воспроизводимость.
Этот шаг включал в себя:
-
Анализ документации по валидации заявок, содержащей 28 ключевых сценариев проверки корректности входящих сообщений
-
Подготовку структурированных данных в формате JSON, содержащую информацию о каждом сценарии валидации
-
Создание Jinja2-шаблона для автоматической генерации тестов
-
Генерацию отдельных Python-файлов для каждого из 26 реализуемых сценариев валидации
-
Анализ и правку тестов, устранение проблем с форматами полей и дублирующих тестов
-
Проведение анализа сценариев и удаление тех, которые невозможно реализовать в текущей тестовой системе
-
Проверку на дублирование и удаление дублирующих тестов
-
Тщательную проверку и отредактирование всех сценариев для обеспечения корректности и соответствия требованиям валидации протокола 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 интеграционных тестов
Эволюция архитектуры третьего шага:
-
Первая попытка: AI создал отдельный класс с валидирующими функциями
-
Проблема: логи были нечитаемые из-за абстракций
-
Упрощение: убрали класс, сделали подход без абстракций
-
Возврат к решению: вернулись к структурированному подходу, но с параметризацией
-
Финальное решение: добавили параметризацию тестов для покрытия различных сценариев
Метрики улучшения:
-
Время отладки: с ~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 больших моделей или мощное железо для локального запуска.
Практический совет: начните с доступных вам моделей среднего размера и фокусируйтесь на улучшении промптов и декомпозиции задач, прежде чем переходить к более крупным моделям.
Перспективы развития
В потенциале весь описанный процесс можно полностью автоматизировать:
-
Автоматический парсинг новых версий спецификации при их обновлении
-
Инкрементальная генерация тестов только для изменённых разделов протокола
-
CI/CD интеграция с автоматическим запуском сгенерированных тестов
-
Самообучение системы на основе найденных багов — превращение их в новые тестовые сценарии
Такая система позволит поддерживать актуальность тестового покрытия без ручного вмешательства, автоматически адаптируясь к эволюции протокола.
Автоматизация самой автоматизации
Важно отметить, что большую часть работы с промптами, документацией и ручными операциями можно автоматизировать. В нашем проекте мы выполняли многие шаги вручную — копировали промпты, парсили документацию, исправляли ошибки. Однако современная экосистема AI-инструментов предлагает готовые решения для таких задач.
MCP (Model Context Protocol) и аналогичные инструменты позволяют:
-
Автоматически предоставлять контекст из документации и кодовой базы
-
Создавать переиспользуемые инструменты для работы с кодом
-
Строить пайплайны генерации без ручного копирования промптов
-
Интегрировать валидацию и проверки прямо в процесс генерации
Современные подходы к автоматизации:
-
AI-агенты с доступом к файловой системе и инструментам разработки
-
RAG-системы для автоматического извлечения релевантного контекста из документации
-
Orchestration frameworks (LangChain, CrewAI) для построения сложных пайплайнов
-
Code analysis tools с AI-интеграцией для автоматической валидации
-
CI/CD интеграции с триггерами на обновление спецификаций
Область AI-assisted development стремительно развивается. То, что год назад требовало ручной работы, сегодня можно автоматизировать готовыми инструментами. Поэтому даже описанный нами процесс — это лишь отправная точка. В будущем весь цикл от обновления документации до генерации и валидации тестов может происходить полностью автоматически, требуя вмешательства человека только для финального ревью и принятия решений.
Автор: Lanun


