Привет, Хабр! Меня зовут Александр, я из Сбера, лидер по автоматизации в Департаменте Сервисы и Безопасности. В тестировании я около 13 лет, и последние лет 10 занимаюсь автоматизацией и её развитием в своём подразделении.
В этой статье расскажу, как с помощью IDE, LLM и RAG‑подхода можно автоматизировать одну из самых рутинных задач автоматизаторов — разработку новых автотестов по ручным сценариям, и при этом сохранять стиль и архитектуру проекта.
Где мы сейчас: ИИ уже в разработке, но почти не в автотестах
По даннымнедавнего опроса StackOverflow, 84% разработчиков постоянно используют ИИ‑ассистенты в своей работе. Для них это такой же привычный инструмент, как IDE или Git.
Как обычно работает подобный ИИ‑помощник в IDE:
- анализирует открытые файлы;
- дополнительно смотрит на соседние файлы в проекте;
- учитывает локальный контекст: какие вопросы вы задавали и какой код меняли.
А вот автотестировщики часто оказываются как будто «на обочине» этой ИИ‑революции.
А как же автотесты?
Про них зачастую вспоминают в последнюю очередь: «Главное, чтобы разработка шла быстро!» При этом очевидная зависимость — чем больше кода, тем больше нужно тестов — почему-то чаще всего волнует только тестировщиков.
Представим типичный проект с автотестами: есть класс с тестами на JUnit 5, класс с шагами и класс с утилитами. Автотесты пишем на Java, в JetBrains IDE, используем Allure для отчётности. Типичный API‑тест у нас выглядит так:
Здесь есть:
- ключ ручного теста из TMS;
- любимый Allure;
- общий метод проверки кода ответа.
Автотесты — это тоже код. Часто не проще, а сложнее боевого приложения, и обладают теми же свойствами:
- могут иметь сложную архитектуру (например,PageObjectдля UI);
- требуют поддержки и рефакторинга;
- живут в Git‑репозитории, часто со своим CI.
Постановка задачи: из ручного теста в автотест
Допустим, к нам на автоматизацию пришёл новый ручной тест:
Действие:отправить POST‑запрос по адресуhttps://jsonplaceholder.typicode.com/postsТестовые данные:{"title":"foo","body":"bar","userId":1}Ожидаемый результат:код ответа 200
Что делает обычный автоматизатор?
- Вспоминает, есть ли похожие тесты.
- Смотрит, как в этом проекте принято писать тесты.
- Пишет новый тест в стиле проекта.
Раз уж у разработчиков есть ИИ‑ассистенты, то давайте попробуем сделать то же самое для автотестов: возьмём LLM, попросим её «превратить» ручной тест в автотест и посмотрим, что получится.
Пример автотеста, который может сгенерировать модель:
Код, скорее всего, даже заработает. Но:
- нет Allure;
- нарушен стиль наименования тестов;
- URL и тело запроса инициализируются «не по‑нашему»;
- используется стандартная проверка статуса вместо нашегоcheckCode;
- модель добавила лишние проверки, которых не было в ручном тесте.
Почему «прямая» генерация не работает
Наивный подход «отправить текст теста в LLM и взять результат» плохо подходит для реальных проектов:
- Интеграция дольше, чем ручная разработка.Нужно выкинуть лишнее, дописать нужное, подогнать под стиль и инфраструктуру.
- Модель не знает контекст проекта.Фреймворки, обёртки, базовые утилиты, соглашения по именованию — всё это теряется.
- Автотесты — не «обычный» код.Свои фреймворки, паттерны и ограничения.
Допустим, мы решили: «Ок, давайте отдадим модели весь проект: классы, шаги, утилиты, тесты». Сразу упираемся в технические ограничения:
- В контекстное окно не помещается весь код, даже если его нарезать.
- Запросы обрабатываются долго, и обогащение контекста тоже.
- При использовании популярных облачных моделей это ещё и дорого.
- Разворачивать у себя большую open source‑модель — дорого по «железу».
Плюс проблемы качества:
- Галлюцинации (комментарии вместо кода, пропуск шагов).
- Потеря контекста при больших объёмах.
- Нестабильный результат: иногда нужно несколько перегенераций.
Few-shot: умнее, но всё ещё недостаточно
Следующий шаг — перейти от «наивной» генерации к few‑shot: показывать модели примеры уже существующих тестов и шагов.
Идея:даём модели только релевантные примеры из нашего проекта, и она генерирует тест «в нашем стиле».Разобьём промпт на две части.
Системный промпт:
- роль модели;
- задача;
- формат входных и выходных данных.
Модель генерирует, например, такой тест:
Почти идеально:
- появился вызов Allure;
- ссылка на сервис там, где нужно;
- имя теста соответствует нашему паттерну;
- используется нашcheckCode;
- никаких лишних проверок.
Но у статичного few‑shot есть фундаментальный недостаток:плохая масштабируемость.В реальном проекте — тысячи тестов. Для каждого нового ручного теста надо вручную подбирать набор «примеров», чтобы они были релевантны. Это долго и плохо автоматизируется.
Переходим к RAG: динамический поиск примеров
Решение — перейти от статичных примеров к динамическомуRAG‑подходу (Retrieval Augmented Generation). Идея:
- Заранее сделать базу знаний по проекту.
- По входящему ручному тестуискать по смыслурелевантные шаги и тесты;
- Подставлять найденные примеры в промпт автоматически;
- На выходе получать автотест «как будто его писал человек в этом проекте».
Чтобы это работало, нам нужно:
- Сформировать базу знаний о проекте.
- Настроить семантический поиск по этой базе.
- Интегрировать всё это в IDE, чтобы генерация была «в один клик».
Немного теории: эмбеддинги, метаданные и векторное хранилище
Основные понятия:
- Эмбеддинг— преобразование текста в числовой вектор. Специальная модель‑энкодер разбивает текст, анализирует значения и связи между словами и «упаковывает» смысл в вектор.
- Метаданные— произвольные данные, которые мы храним рядом с вектором: код шага, место его вызова, файл, имя метода и т. п.
- Векторное хранилище— база, которая умеет:хранить векторы и метаданные;быстро искать «похожие» векторы по косинусному расстоянию или другой метрике.
Общий план:
- Просканировать проект в IDE.
- Собранные данные (шаги, тесты) превратить в векторы.
- Сохранить в векторную базу.
Почему всё это встраиваем в IDE
Чтобы сканирование и работа с кодом были удобными и точными, мы делаем это в виде плагина для JetBrains IDE. IDE видит проект не как «текст», а как структуру PSI.
Упрощённо структура выглядит так:
- PsiElement— базовый элемент (любая сущность или знак в файле);
- PsiAnnotation— любая аннотация у метода;
- PsiMethod— сам метод, включая аннотации, тело, комментарии;
- PsiReference/PsiReferenceExpression— ссылки на методы (то, что мы видим поCtrl+Click);
- ElementVisitor— «сканер», который рекурсивно обходит дерево PSI‑элементов.
Это даёт нам точный контроль над тем, что мы ищем и как это сохраняем.
Сканируем проект: сначала шаги
Мы хотим собрать:
- все шаги Allure (по аннотации@Step);
- все вызовыAllure.step(...).
Пример теста:
- Ищем класс аннотации шага:PsiClass stepAnnotation = findClass("io.qameta.allure.Step");
- Находим все методы с этой аннотацией и все их использования (PsiReference).
- Отдельно проходимся по проектуJavaRecursiveElementVisitorи ищем статические вызовыAllure.step(...):psiJavaFile.accept(new JavaRecursiveElementVisitor() { @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { // Проверяем: "Allure.step"? } });Для static‑шагов описанием считаем первый параметрAllure.step("описание шага", ...), а примером использования — сам вызов.
Сканируем проект: теперь тесты
Шаги собрали, теперь нужны сами автотесты. Обычно тестов больше, чем шагов, поэтому для быстроты используем механизмы индексации IDE. Мы хотим для каждого теста:
- ключ в TMS (например,"MYKEY-T1"из@TmsLink);
- код теста;
- краткое текстовое описание (для семантического поиска).
Алгоритм индексации:
С кодом теста есть проблема:он не является его «смысловым» описанием.Поэтому мы:
- Передаём код теста в LLM с простым запросом «Объясни кратко этот тест».
- Получаем лаконичное текстовое описание, например: «Проверка отправки GET‑запроса по адресу с проверкой успешного ответа HTTP‑статуса 200».
- Именно это описание используем для эмбеддинга и семантического поиска.
Эмбеддинг и хранение
Что мы эмбеддим:
- для каждого шага — его текстовое описание;
- для каждого теста — краткое текстовое описание.
В метаданные кладём:
- исходный код шага/теста;
- место в проекте;
Для прототипа достаточно хранить всё в памяти (или в файле между перезапусками IDE). Для серьёзного решения лучше использовать специализированную векторную БД.
Алгоритм поиска примеров
База готова, теперь нужно научиться по новому ручному тесту находить подходящие шаги и тесты. Разобьём задачу на два этапа:
- поиск шагов;
- поиск тестов.
Поиск шагов
Ручной тест может состоять из множества шагов. Минимально каждый из них — это действие и ожидаемый результат.
Чтобы повысить шанс найти что‑то похожее, для каждого шага делаем три текстовые комбинации:
- только действие;
- только ожидаемый результат;
- действие + ожидаемый результат.
Каждую комбинацию эмбеддим и ищем ближайшие по смыслу шаги в ранее созданной базе эмбендингов.
Поиск тестов
С тестами сложнее: сумма всех шагов — это не «смысл» теста. Текст шагов отличается от краткого описания. Поэтому:
- Получаем от LLM краткое описание ручного теста.
- Эмбеддим это описание.
- Ищем в базе эмбендингов существующий автотест с ближайшим по смыслу описанием.
При поиске учитываем степень сходства (score). Если, например, ищем «Проверить код 200», а находим четыре подходящих шага, то используем этот score, чтобы выбрать лучшего кандидата.
Финальный запрос к LLM
Когда мы нашли подходящие шаги и тесты, остаётся правильно сформировать запрос к модели. Вместо статичных блоков few‑shot подставляем параметры, которые заполняются автоматически:
Дальше есть две важные проверки:
- Проверка LLM‑результата самой моделью.Делаем дополнительный запрос: отдаём сгенерированный код и просим модель перепроверить результат по нашим правилам.
- Проверка на уровне IDE через PSI.Здесь мы уже программно убеждаемся, что:
- Синтаксис Java корректный;
- Нужные аннотации на месте;
- Ключи тестов и другие чувствительные данные не потерялись и не «исказились».
После этого вставляем проверенный код в редактор IDE.
Пример полного цикла на новом тест
Вернёмся к нашему ручному тесту:
- Действие:отправить POST‑запрос кhttps://jsonplaceholder.typicode.com/posts
- Тестовые данные:{"title":"foo","body":"bar","userId":1}
- Ожидаемый результат:код ответа 200
- Разбиваем шаг на три текстовых варианта:«Отправить POST‑запрос»;«Код ответа 200»;«Отправить POST‑запрос + код ответа 200».
- Для этих вариантов ищем похожие шаги в базе и находим, например, метод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. Сразу предупрежу: там нет всего кода, но есть основа для старта