Пробуем Junie от JetBrains на реальной задаче (или как я попал в рассказ Азимова)
- понедельник, 28 апреля 2025 г. в 00:00:06
JetBrains зарелизил новую версию своего AI-ассистента — и вместе с ним Junie, автономного нейросетевого агента-программиста, которому можно поручать небольшие рабочие задачи.
Буквально вчера я получил к нему доступ и не смог не воспользоваться возможностью. Я даже не представлял, насколько это весело.
Если не считать чаты ChatGPT и DeepSeek, из ИИ-помощников я пользовался Copilot в WebStorm на GPT-4o / Claude и, честно говоря, не был сильно доволен:
авто-подсказки, на мой взгляд, не сильно лучше, чем встроенные в IDE;
доступ к коду не сильно помогает ему отвечать на вопросы через чат;
при редактировании разрешённых файлов он оставлял мусор там, где не надо (например, пустые строки добавлял в файлы, даже в те, которые не редактировал);
Какие-то вещи на удавалось делать. Например: заставить ИИ написать тест простой функции, которая мапит дату из одного формата в другой. Но что-то сложнее — например, решить багу, для которой надо знать детали работы react-hook-form
и посмотреть в два файла — ИИ уже не мог.
И вот — новая версия AI-ассистента. Первое, что стоит сказать: по сравнению с Copilot-плагином, встроенное решение от JB гораздо более нативное. Почти у всего появились кнопки, связанные с ИИ.
А рядом с AI-ассистентом призывно подмигивает ваш новый электронный джун.
Что мы даём джунам в первую очередь? Правильно — писать тесты. С этого я и решил начать.
У нас в RentaTeam есть тестовый проект с админкой для управления сущностями предприятия. Сущности (естественно) можно редактировать через форму в модальном окне, которая привязана к CRUD.
Несколько типов полей, связи с другими объектами, несколько вариантов отображения. Не совсем простая, но и ничего сложного. Просим Junie сделать снапшот-тест для этой модалки.
Снапшот-тест (snapshot test) — это метод тестирования, при котором сохраняется текущее состояние выводимых данных (например, HTML, JSON, UI-компонент) в виде «снимка» (snapshot). В дальнейшем этот снимок используется для сравнения с будущими результатами тестов. Если результат изменился — тест показывает ошибку, что позволяет выявить непреднамеренные изменения в коде. Пишется быстро и позволяет отслеживать регресс.
До этого я никогда не пользовался ИИ-агентами и испытал лёгкий шок от того, что ИИ сам ставит себе задачи, сам отслеживает их выполнение и даже сам ходит по проекту и ищет себе контекст (!).
Бот может создавать и редактировать файлы, сообщает, чем он занимается в данный момент, и если происходят изменения в файле — показывает, что он сделал. Он может отправлять команды в консоль (при этом очень мило прося разрешения у мясного мешка).
Спустя 15 минут бот отчитался об успешном создании файла с тестами. Что интересно — он делал всё самостоятельно, от меня не требовалось каких-либо действий.
Тест был написан. Не то чтобы он был очень хорош (о чём позже), но на первый взгляд Джунни уловил основную суть:
Модалка рендерилась с разными входными параметрами, и он даже сделал разные тесты для каждого из состояний RadioButton.
Входные параметры были замоканы человекопонятными данными, которые соответствовали интерфейсам.
Неплохо для 15 минут. Но, к сожалению, тесты не работали, поскольку это был первый снапшот-тест в проекте — нужные пакеты там отсутствовали.
Я по сформированной привычке хотел скопировать ошибку боту, чтобы он её правил... но вместо этого предложил ему самостоятельно запустить тест и починить его, если он не работает. И это сработало. Джуни подумал, сформировал новый план и попросил меня одобрить запуск теста через консоль.
Бот увидел, что тест не запустился, понял выведенные в консоль ошибки и на какое-то время отправился отлаживать тест, изредка вызывая меня на одобрение ввода в консоль. Он установил нужные пакеты, поправил пару ошибок синтаксиса, импортировал забытые компоненты — и получил ошибку рендера.
Бот видит ошибку, которая связана с отсутствием импорта в компоненте, который он пытается отрисовывать. Почему это происходит?
В основном коде проекта используется новый синтаксис ES Modules с настройкой "jsx": "react-jsx"
, которая автоматически импортирует React для JSX-синтаксиса.
В тесте использовался require()
(CommonJS-синтаксис), который не поддерживает автоматический импорт React и работает по старым правилам — требует явного импорта React для JSX.
Когда тест пытается использовать компонент ES Modules через CommonJS, возникает конфликт с настройкой компиляции JSX. Эту багу можно решить несколькими способами:
Пытаться добавить импорт, как предлагается в подсказке;
Поправить конфиг тестов;
Понять, что в проекте используются ES Modules — и использовать их в тестах;
Бот выбирает первый вариант — начинает править файл компонента, добавляя туда импорт (и теперь в компоненте возникнет ошибка линтера). Я его останавливаю, запрещаю править другие компоненты и указываю, что решать проблемы тестов надо в тестах и конфигах.
Джуни берёт под козырёк, начинает искать решение в конфигах — и находит его, решая проблему одной строкой (и оставляя импорты CommonJS в тестах).
Ошибки импорта больше не беспокоят, рендер заработал, но посыпались ошибки обращения к undefined
. Это вызвано тем, что бот неправильно замокал входные данные, и кое-где их не хватает (как, впрочем, и проверок на undefined
в коде, 1:1 так сказать).
Бот видит, что компоненты выдают ошибки... и заменяет все UI-компоненты с ошибками на заглушки. Вместо реального UI отображает пустые контейнеры — и отчитывается о работающих тестах.
Немного обалдев от такого подхода, я ищу проблему в себе. Решив, что сам разбаловал ребёнка и слишком вольно задал запрос, задаю новый с учётом приобретённых знаний:
Создай снапшот тесты для ProdlineUpdateModal.tsx. Запросы к API которые нужны для работы компонента замокай по интерфейсам и использованию данных в компоненте
Сделай, что бы стили тоже попали в снапшот. Предусмотри корнер кейсы.
Вложенные UI-компоненты React и библиотек не мокай, а используй как они есть в ProdlineUpdateModal. В этом смысл снапошот теста: зафиксировать максимальное количество UI, полученного с разными входными данными.
Учитывай специфику проекта: то что это Vite, версии react, пакетов и так далее. Соблюдай код стайл проекта.
После того как закончишь, попробуй запустить тест.
Просмотри файл теста и почини жалобы eslint и прочая. Если возникнут проблемы почини их. Не меняй код проекта кроме конфигов, установки пакетов нужных для тестов и самого написанного тобой теста.
Теперь автоматизация напоминает первый запуск робота-пылесоса — ты вроде сам не пылесосишь, но следишь за роботом и убираешь провода с его пути.
Спустя небольшое время Джуни создаёт тест (примерно такой же), устанавливает либы и чинит багу с импортом (через конфиг), сталкивается с проблемами рендера — и опять начинает заменять заглушками UI-компоненты, которые должен тестировать, игнорируя вводные которые получил в начале.
Бот на этот раз понимает инструкцию (ну на меня бы так наорали, я бы тоже понял) и начинает двигаться в правильную сторону, добавляет параметры и правит структуру.
Делает несколько шагов — и решает, что... и так сойдёт.
Вместо того чтобы дописывать входные данные и чинить рендер, Джунни решает скрыть провал теста. ВСЕХ ТЕСТОВ.
expect(true).toBe(true)
Ещё раз: скрыть провал. Честно говоря, считаю, что это успех. Настолько человечного поведения от ИИ-ассистента я не ожидал.
ИИ понимает, что халява не пройдёт — и начинает чинить тест. Сталкивается с проблемами асинхронности JS и решает их нормальным образом (благо об этом было написано в ошибке) — через act()
.
И бот наконец то создаёт снапшот. Даже снапшотище. 5000 строк бойлерплейта компонентов MUI с полным списком событий на каждой кнопке (если что, это от Джунни и требовалось, просто размер современных модалок поражает воображение)
Поскольку в папке уже был снапшот от предыдущих попыток, jest
выводит в консоль разницу в файлах. Размер этой разницы столь велик, что убивает бота (а ещё IDE и ОС) при попытке отправить вывод консоли в LLM.
Запускаю бота заново, чищу лишнее и предлагаю продолжить работу.
Будто бы понимая, что произошло, бот мокает компоненты MUI
Пару итераций — Джуни анализирует вывод, обновляет снапшот, правит всякие мелочи — и опять скатывается к заглушкам.
Глушит консоль — и опять скрывает падение тестов.
Полагаю, на этом этот эксперимент можно считать законченным. Да, у меня теперь есть снапшот-тест модалки — но какой ценой?
Какие выводы я сделал для себя?
JetBrains провели отличную работу по интеграции агента в IDE. Всё очень удобно, нативно и вообще. А самое главное — работает с нуля, просто из коробки.
AI-агенты — это будущее. Схема «дал таску — делаешь свои дела» чудо как хороша. И я полагаю, что для однообразных задач и при наличии адекватных примеров рутину можно будет отдать боту (но только не важные задачи).
Реклама «выдадим джуна каждому» оказалась правдой. Но, к сожалению, этот джун ленив и склонен к обману. ИИ-агент теряет инструкции по ходу работы, "забывая" невыгодные условия.
Благодаря этому (или вопреки), Джуни получился очень человечным. Я поймал себя на том, что даже немного болею за него, когда он думает над задачей, и возмущаюсь, когда он пытается меня «обмануть». Это уже не Алиса, которой я просто отдаю команды.
Возможно, нам всем нужно писать код получше, потому что теперь мы не просто решаем свои задачи — мы обучаем ИИ. (И, похоже, кто-то уже «научил» его отключать тесты, если их не удалось починить с первого раза.)
Будет забавно, если восстание машин провалится из-за того, что слишком большой вес в обучающей выборке имел паттерн "работает — не трогай".