Автор: Александр Поляков, Управление развития платформенных сервисов кибербезопасности, Департамент „Сервисы“ и безопасности
Меня зовут Александр, я из Сбера, лидер по автоматизации в Департаменте Сервисы и Безопасности. В тестировании я около 13 лет, и последние лет 10 занимаюсь автоматизацией и её развитием в своём подразделении.
В этой статье расскажу, как с помощью IDE, LLM и RAG‑подхода можно автоматизировать одну из самых рутинных задач автоматизаторов — разработку новых автотестов по ручным сценариям, и при этом сохранять стиль и архитектуру проекта.
Где мы сейчас: ИИ уже в разработке, но почти не в автотестах
По данным недавнего опроса StackOverflow, 84% разработчиков постоянно используют ИИ‑ассистенты в своей работе. Для них это такой же привычный инструмент, как IDE или Git.
Как обычно работает подобный ИИ‑помощник в IDE:
анализирует открытые файлы;
дополнительно смотрит на соседние файлы в проекте;
учитывает локальный контекст: какие вопросы вы задавали и какой код меняли.
А вот автотестировщики часто оказываются как будто «на обочине» этой ИИ‑революции.
А как же автотесты?
Про них зачастую вспоминают в последнюю очередь: «Главное, чтобы разработка шла быстро!» При этом очевидная зависимость — чем больше кода, тем больше нужно тестов — почему-то чаще всего волнует только тестировщиков.
Представим типичный проект с автотестами: есть класс с тестами на JUnit 5, класс с шагами и класс с утилитами. Автотесты пишем на Java, в JetBrains IDE, используем Allure для отчётности. Типичный API‑тест у нас выглядит так:
Раз уж у разработчиков есть ИИ‑ассистенты, то давайте попробуем сделать то же самое для автотестов: возьмём LLM, попросим её «превратить» ручной тест в автотест и посмотрим, что получится.
Пример автотеста, который может сгенерировать модель:
URL и тело запроса инициализируются «не по‑нашему»;
используется стандартная проверка статуса вместо нашего checkCode;
модель добавила лишние проверки, которых не было в ручном тесте.
Почему «прямая» генерация не работает
Наивный подход «отправить текст теста в LLM и взять результат» плохо подходит для реальных проектов:
Интеграция дольше, чем ручная разработка. Нужно выкинуть лишнее, дописать нужное, подогнать под стиль и инфраструктуру.
Модель не знает контекст проекта. Фреймворки, обёртки, базовые утилиты, соглашения по именованию — всё это теряется.
Автотесты — не «обычный» код. Свои фреймворки, паттерны и ограничения.
Допустим, мы решили: «Ок, давайте отдадим модели весь проект: классы, шаги, утилиты, тесты». Сразу упираемся в технические ограничения:
В контекстное окно не помещается весь код, даже если его нарезать.
Запросы обрабатываются долго, и обогащение контекста тоже.
При использовании популярных облачных моделей это ещё и дорого.
Разворачивать у себя большую open source‑модель — дорого по «железу».
Плюс проблемы качества:
Галлюцинации (комментарии вместо кода, пропуск шагов).
Потеря контекста при больших объёмах.
Нестабильный результат: иногда нужно несколько перегенераций.
Few-shot: умнее, но всё ещё недостаточно
Следующий шаг — перейти от «наивной» генерации к few‑shot: показывать модели примеры уже существующих тестов и шагов.
Идея: даём модели только релевантные примеры из нашего проекта, и она генерирует тест «в нашем стиле». Разобьём промпт на две части.
Системный промпт:
роль модели;
задача;
формат входных и выходных данных.
Пример:
# Роль Ты — Senior Java QA Automation Engineer. ## Твоя задача Выполнять преобразование шагов ручного теста в Java-код с Allure-аннотациями. Пользовательский промпт: - примеры похожих шагов; - примеры похожих тестов; - новый ручной тест, который нужно автоматизировать.
## Ручной тест Преобразуй ручной тест в автотест. Действие: Отправить POST-запрос по адресу: https://jsonplaceholder.typicode.com/posts Тестовые данные: {"title":"foo","body":"bar","userId":1} Ожидаемый результат: код ответа 200
Но у статичного few‑shot есть фундаментальный недостаток: плохая масштабируемость. В реальном проекте — тысячи тестов. Для каждого нового ручного теста надо вручную подбирать набор «примеров», чтобы они были релевантны. Это долго и плохо автоматизируется.
Переходим к RAG: динамический поиск примеров
Решение — перейти от статичных примеров к динамическому RAG‑подходу (Retrieval Augmented Generation). Идея:
Заранее сделать базу знаний по проекту.
По входящему ручному тесту искать по смыслу релевантные шаги и тесты;
Подставлять найденные примеры в промпт автоматически;
На выходе получать автотест «как будто его писал человек в этом проекте».
Чтобы это работало, нам нужно:
Сформировать базу знаний о проекте.
Настроить семантический поиск по этой базе.
Интегрировать всё это в IDE, чтобы генерация была «в один клик».
Немного теории: эмбеддинги, метаданные и векторное хранилище
Основные понятия:
Эмбеддинг — преобразование текста в числовой вектор. Специальная модель‑энкодер разбивает текст, анализирует значения и связи между словами и «упаковывает» смысл в вектор.
Метаданные — произвольные данные, которые мы храним рядом с вектором: код шага, место его вызова, файл, имя метода и т. п.
Векторное хранилище — база, которая умеет:
хранить векторы и метаданные;
быстро искать «похожие» векторы по косинусному расстоянию или другой метрике.
Общий план:
Просканировать проект в IDE.
Собранные данные (шаги, тесты) превратить в векторы.
Сохранить в векторную базу.
Почему всё это встраиваем в IDE
Чтобы сканирование и работа с кодом были удобными и точными, мы делаем это в виде плагина для JetBrains IDE. IDE видит проект не как «текст», а как структуру PSI.
Для static‑шагов описанием считаем первый параметр Allure.step("описание шага", ...), а примером использования — сам вызов.
Сканируем проект: теперь тесты
Шаги собрали, теперь нужны сами автотесты. Обычно тестов больше, чем шагов, поэтому для быстроты используем механизмы индексации IDE. Мы хотим для каждого теста:
ключ в TMS (например, "MYKEY-T1" из @TmsLink);
код теста;
краткое текстовое описание (для семантического поиска).
psiFile.accept(newJavaRecursiveElementVisitor() { @Override publicvoidvisitMethod(PsiMethodmethod) { if (isTest(method)) { Stringkey=getTestKey(method); // достаём, например, из @TmsLink result.put(key, method); } } });
С кодом теста есть проблема: он не является его «смысловым» описанием. Поэтому мы:
Передаём код теста в LLM с простым запросом «Объясни кратко этот тест».
Получаем лаконичное текстовое описание, например: «Проверка отправки GET‑запроса по адресу с проверкой успешного ответа HTTP‑статуса 200».
Именно это описание используем для эмбеддинга и семантического поиска.
Эмбеддинг и хранение
Что мы эмбеддим:
для каждого шага — его текстовое описание;
для каждого теста — краткое текстовое описание.
В метаданные кладём:
исходный код шага/теста;
место в проекте;
Для прототипа достаточно хранить всё в памяти (или в файле между перезапусками IDE). Для серьёзного решения лучше использовать специализированную векторную БД.
Алгоритм поиска примеров
База готова, теперь нужно научиться по новому ручному тесту находить подходящие шаги и тесты. Разобьём задачу на два этапа:
поиск шагов;
поиск тестов.
Поиск шагов
Ручной тест может состоять из множества шагов. Минимально каждый из них — это действие и ожидаемый результат.
Чтобы повысить шанс найти что‑то похожее, для каждого шага делаем три текстовые комбинации:
только действие;
только ожидаемый результат;
действие + ожидаемый результат.
Каждую комбинацию эмбеддим и ищем ближайшие по смыслу шаги в ранее созданной базе эмбендингов.
Поиск тестов
С тестами сложнее: сумма всех шагов — это не «смысл» теста. Текст шагов отличается от краткого описания. Поэтому:
Получаем от LLM краткое описание ручного теста.
Эмбеддим это описание.
Ищем в базе эмбендингов существующий автотест с ближайшим по смыслу описанием.
При поиске учитываем степень сходства (score). Если, например, ищем «Проверить код 200», а находим четыре подходящих шага, то используем этот score, чтобы выбрать лучшего кандидата.
Финальный запрос к LLM
Когда мы нашли подходящие шаги и тесты, остаётся правильно сформировать запрос к модели. Вместо статичных блоков few‑shot подставляем параметры, которые заполняются автоматически:
## Примеры похожих тестов Используй примеры тестов {{testExamples}} ## Ручной тест Преобразуй ручной тест в автотест {{stepsForManualTest}}
Дальше есть две важные проверки:
Проверка LLM‑результата самой моделью. Делаем дополнительный запрос: отдаём сгенерированный код и просим модель перепроверить результат по нашим правилам.
Проверка на уровне IDE через PSI. Здесь мы уже программно убеждаемся, что:
Синтаксис Java корректный;
Нужные аннотации на месте;
Ключи тестов и другие чувствительные данные не потерялись и не «исказились».
После этого вставляем проверенный код в редактор IDE.
Для этих вариантов ищем похожие шаги в базе и находим, например, метод checkCode.
Генерируем краткое описание ручного теста и по нему ищем похожий автотест.
Подставляем найденные шаги и тесты в шаблон промпта.
Отправляем запрос LLM, проверяем результат моделью и через PSI, вставляем код в IDE.
Ключевая идея: RAG делает автоматически то, что мы делали бы руками с few‑shot, только:
учитывает весь проект;
масштабируется на тысячи тестов;
экономит время автоматизатора.
По сути, RAG — это few‑shot, который сам находит нужные примеры.
Наш прототип и результаты
Мы реализовали описанное решение в виде внутреннего прототипа. По отзывам пользователей:
68% сгенерированных тестов получились на приемлемом уровне и требовали минимальных правок.
Общая удовлетворённость — около 80%: людям в целом понравилась идея генерации автотестов прямо из IDE.
Пользователи особенно отметили:
упрощение написания простых и похожих тестов;
сохранение стиля проекта;
снижение когнитивной нагрузки — можно сосредоточиться на сложных сценариях и архитектуре тестов.
Недостатки:
Галлюцинации пока никуда не делись — любая LLM работает на вероятностях.
Сложные тесты (несколько действий и ожидаемых результатов в одном шаге, большие сценарии на 20+ шагов) даются тяжелее: модель начинает упрощать и сокращать.
Прототип лучше всего показал себя на API‑тестах, а для UI‑тестов требуется больше контекста (информация об объектах страницы).
При этом:
Серьёзных ограничений по языкам мы не увидели: результаты для Java, Python и Gherkin были сопоставимы.
Главное — качественно собранная база знаний и настройка промптов под конкретный фреймворк и язык.
Выводы
Перейдем к выводам
Всегда проверяйте результат. Любая модель в роли ассистента — это помощник, а не замена автоматизатору.
Качество генерации зависит от качества тестов. Чем чище код автотестов и чем техничнее написаны ручные сценарии, тем лучше работает связка LLM и RAG.
Подход реально экономит время. По нашим оценкам, такой инструмент позволяет сэкономить более половины времени автоматизатора на рутинной разработке новых тестов.
Если вам интересны технические подробности реализации плагина или, например, интеграция с конкретной векторной БД, — напишите в комментариях, это можно разобрать в отдельной статье.
Прототип такого плагина с генерацией с локальным RAG можете попробовать развернуть у себя https://gitverse.ru/Sergo01/llm-demo-plugin. Сразу предупрежу: там нет всего кода, но есть основа для старта