Главное за 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 стремительно развивается. То, что год назад требовало ручной работы, сегодня можно автоматизировать готовыми инструментами. Поэтому даже описанный нами процесс — это лишь отправная точка. В будущем весь цикл от обновления документации до генерации и валидации тестов может происходить полностью автоматически, требуя вмешательства человека только для финального ревью и принятия решений.
RodionGork
Да с тестами проблема не в том чтобы их напедалить, а в том чтобы в этой порнографии спустя месяц, два, год можно было понять к чему это напедалено, что и как проверяется.
Я б избегал пользоваться услугами финансовой биржи где к созданию тестов относятся так, гм, философски :)))