Кому будет полезно
Фронтенд-разработчикам, которые хотят мигрировать тесты с Enzyme на React Testing Library
Тем, кто экспериментирует с LLM для генерации кода
Тем, кому интересен практический опыт построения мультиагентных систем
⚠️ Дисклеймер. Статья полностью про фронтенд: React, Jest/Vitest, React Testing Library. И ещё: это просто моя честная история, как было. Можно было сделать быстрее, можно было думать по-другому, но я рассказываю как есть, со всеми ошибками и тупиками.
Контекст
В первой статье should render — и что? Как мы перестали тестировать разметку и начали тестировать поведение я рассказал про философию тестирования: почему снэпшоты и Enzyme ведут в тупик, зачем переходить на React Testing Library и как я провёл митап для команды. Там был чек-лист, были примеры плохих и хороших тестов, было ясное понимание как хотим и как не хотим.

Понимание, как писать правильно, это одно. Реальность совсем другое. Передо мной стояли сотни компонентов, и на каждый нужно было написать тесты с нуля. Тесты типа should render в расчёт не беру. Руками это месяцы работы, которые никто не выделит в спринт. И я начал думать: как это можно автоматизировать?
Эта статья про путь от наивного "закинул компонент в ChatGPT" до рабочего мультиагентного пайплайна, который генерирует тесты, ревьюит их по философии RTL и верифицирует через мутации.
Этап 0: наивный подход, "просто спроси LLM"
Первые попытки были максимально простыми. Скопировал код компонента, вставил в ChatGPT, попросил написать тесты.
Результат оказался предсказуемо плохим. Модель не знала ни контекста проекта, ни наших правил, ни философии тестирования. Она генерировала что-то, что выглядело как тесты, но по факту:
Тесты не проходят
TypeScript ошибки на каждом шагу
Много анти-паттернов, от которых я пытался уйти
Ничего не знало про наши моки, стор, роутинг
Без контекста, без правил, без философии LLM просто галлюцинировала "среднестатистические тесты из интернета".

Этап 1: пайплайн в N8N, первая попытка системного подхода
Я решил, что дело в промпте и контексте. Если дать модели достаточно контекста, она должна справиться.
Собрал пайплайн в N8N. Подключил ChatGPT 4.5 через API. Написал большой системный промпт: кто ты, что делаешь, вот философия тестирования, вот хорошие паттерны, вот плохие, вот код компонента, пиши.
Один огромный запрос со всем контекстом.
Не сработало. Тогда я разбил пайплайн на шаги:
Анализ компонента: определить, нужно ли вообще что-то тестировать и какая тут сложность
Классификация: разбить тесты по группам: простые, средние, сложные
Генерация: написать тесты с подходящими примерами для каждой группы
Стало немного лучше, но результаты всё ещё были не рабочими. Тесты очень редко запускались, TypeScript выдавал кучу ошибок. Главная проблема в том, что модель работала в вакууме. Она видела код одного компонента, но не видела проект. Не могла проверить импорты, не знала про реальные типы, не могла запустить то, что написала.
На этом этапе я забросил идею. Мне казалось, что LLM пока просто не готовы для такой задачи. Хотя в голове даже представил, какой из этого можно сделать продукт, который так и остался на уровне идеи и не дошёл до реализации.
Стартап в голове: три ступени
Уровень 1: веб-интерфейс. Закидываешь код компонента, получаешь тесты. Просто и красиво. Но постфактум понимаю, что обречено на провал, потому что слишком мало контекста. Модель не может ничего проверить, не видит проект.
Уровень 2: расширение для VS Code. Прямо в IDE нажимаешь кнопку рядом с компонентом. Расширение берёт код, подтягивает философию тестирования, создаёт файл с тестами. Как встроенный прогон тестов через Jest/Vitest, только для генерации. Мне казалось, это прям красиво. Но я не понимал, как это реализовать технически.
Уровень 3: инструмент для полной миграции. Расширение сканирует всю кодовую базу, собирает список компонентов и делает полную миграцию одним кликом. Задача на часы, но запускается одной командой.
Красивая картинка, недостижимая на тот момент для меня.

Этап 2: Cursor, ближе, но не то
Через какое-то время я начал пользоваться Cursor. С большим отставанием от общей волны хайпа и выхода IDE. Это уже новый уровень: доступ к проекту, он видел файлы, мог подтягивать контекст.
Я просил: "напиши тесты для этого компонента". Cursor писал. Результаты были лучше, чем у голого ChatGPT, потому что он хотя бы видел импорты и типы. Но без хороших промптов и чётких правил тесты всё равно получались "ниже среднего". Философия RTL, пользовательский подход, правильные моки: всё это нужно было объяснять каждый раз.
Догадался соединить одно с другим. Наработки по промптам из N8N и возможности Cursor, прописал на уровне правил проекта: философию, примеры, приблизительную модель принятия решений.
Казалось бы, должен быть качественный скачок, но на тот момент и этого тоже не случилось. Всё равно тесты были 50/50, с большим количеством правок и ручной работы. Не хватало ни мощности модели, ни структурности, ни правил принятия решений, и за длинное время работы в одном контекстном окне модель начинала галлюцинировать.
Этап 3: Claude Code, и тут картинка наконец складывается
Всё изменилось, когда появился Claude Code с агентами и навыками.

Навыки это чистые наборы инструкций на какую либо тему без рабочих рук которые её выполняют. А вот агенты это уже работяги с выбранной моделью, инструкцией, отдельным окном контекста. И главное весь контект проекта тоже есть Claude Code работает изнутри: видит все файлы, может запускать команды, проверять результат.
Мой "уровень 2" из мечты вдруг оказался реализуемым. Причём сразу с прицелом на третий.
Первый рабочий прототип: 3 агента
Я начал строить мультиагентный пайплайн. Сначала думал, что все участники будут агентами, но оказалось, что агенты могут спавнить только субагентов, а не вызывать других агентов на своём уровне. Поэтому главным оркестратором стал навык unit-tester, который знает полный пайплайн и вызывает нужных агентов в нужном порядке.
Первый прототип:
test-planner читает код компонента, анализирует его тип (простой лист-компонент или контейнер с логикой), планирует тест-кейсы
test-writer получает план, пишет готовый
.test.tsxфайлtest-validator проверяет, что всё компилируется: ESLint, TypeScript, запуск тестов
Плюс конфигурационный файл .test-pipeline.yaml для каждого проекта: где лежат моки, какой стор, Jest или Vitest, какие специфические правила. Это позволяет шарить систему между разными проектами.

И вот здесь случился качественный скачок. По сравнению с N8N-пайплайном и Cursor это был совершенно другой уровень:
Агенты работали с реальным проектом, а не с изолированным куском кода
Они могли проверить то, что написали: запустить тесты, увидеть ошибки
Каждый агент фокусировался на своей задаче, а не пытался сделать всё сразу
Конфиг давал контекст проекта: где моки, как устроен стор, какие конвенции
Этап 4: когда зелёные тесты врут
Валидатор проверял, что тесты запускаются. Но "запускается" ещё не значит "хорошо написан". Тест может пройти и при этом цепляться за детали реализации, использовать антипаттерны или тестировать совсем не то, что важно пользователю.
Так появился четвёртый агент, test-reviewer. Он проверяет написанные тесты по философии RTL:
Тестируем поведение, а не реализацию?
Обращаемся к элементам через роли и лейблы, а не через CSS-классы?
Описания тест-кейсов понятные и информативные?
Используем константы, а не захардкоженные строки?
Ревьюер выставляет оценку от 1 до 10. Если меньше 9, тесты уходят на второй круг: планировщик пересматривает план, писатель переписывает. Максимум 3 попытки.
Этап 5: мутационное тестирование, и вот тут всё по-настоящему изменилось
Это идея, которая перевернула для меня восприятие качества тестов.

Даже если тест проходит ревью по философии RTL и компилируется, как убедиться, что он реально ловит баги? Что это не ложноположительный тест, который просто "проходит" и ни о чём не говорит?
Ответ: мутационное тестирование.
Идея простая. Я ломаю компонент определённым образом и проверяю, что тест это заметил. Если тест прошёл при сломанном компоненте, он бесполезен.
Два новых агента:
mutation-planner
Анализирует написанные тест-кейсы и планирует мутации. Для каждого тест-кейса определяет: что конкретно нужно сломать в компоненте, чтобы этот тест упал.
Например:
Тест проверяет, что кнопка "Отправить" заблокирована при пустом email, значит мутация: убрать проверку
disabledТест проверяет отображение ошибки, значит мутация: убрать рендер сообщения об ошибке
Тест проверяет вызов API, значит мутация: закомментировать вызов
test-verifier
Выполняет мутации строго последовательно (это критически важно). Для каждой мутации:
Запускает тест-кейс, убеждается, что он проходит на неизменённом компоненте ✅
Делает бэкап компонента
Применяет мутацию, ломает компонент по плану
Запускает тест-кейс снова, и он должен упасть ❌
Восстанавливает компонент из бэкапа
Запускает тест-кейс ещё раз, и он должен снова пройти ✅
Если все три шага сходятся, мутация пройдена, тест реально работает. Если тест не упал при сломанном компоненте, это слабый тест-кейс и тесты уходят на доработку.
Целевой показатель: 100% catch rate. Если меньше, писатель дописывает тесты, и мутации прогоняются заново. Максимум 3 попытки.

Итоговая архитектура: React Test Agents
Вот к чему я в итоге пришёл. Семь агентов, шесть шагов:

Распределение моделей
Не все задачи требуют одинаковой "силы" модели:
Opus (мощнее, дороже): планирование, написание и ревью тестов, планирование мутаций. Здесь важно глубокое понимание кода и философии
Sonnet (быстрее, дешевле): валидация (запуск ESLint/TS/тестов) и выполнение мутаций (механическая работа по чёткой инструкции)
Вы можете использовать любого провайдера и любые модели. Важно понимать три вещи: насколько модель умная, насколько хорошо она кодит и сколько стоит.
Седьмой агент: commit-analyzer
Отдельно стоит commit-analyzer. Он встраивает генерацию тестов в обычный рабочий процесс.
commit-analyzer делает одну простую вещь: анализирует коммиты от указанного хэша до HEAD и выдаёт список затронутых компонентов с типом изменений. Мелкие правки, серьёзные изменения или совсем новый компонент.
Дальше в дело вступает основной навык unit-tester, и вот тут начинается самое интересное. Он работает в интерактивном режиме: приходит ко мне в терминал и говорит "для компонента X надо дописать тесты, вот что изменилось, пишем?". Я отвечаю approve или skip. Потом следующий компонент. И так далее.
За одну команду можно пройтись по всем затронутым изменениями файлам. Каждый компонент обрабатывается последовательно. Контекстное окно не забивается, потому что каждый агент работает в своём изолированном контексте. Полный прогон для компонента средней сложности занимает порядка 10 минут работы Claude Code.
Я уже использую это в повседневной работе. Пофиксил баг, написал фичу, запустил тестировщик, прошёлся по изменённым компонентам. Approve, approve, skip, approve. Готово.

Что я понял по пути
1. Контекст решает всё
Один и тот же LLM генерирует мусор без контекста и рабочие тесты с контекстом. Разница между "закинул код в чат" и "агент видит весь проект" как между гуглением рецепта и работой с шеф-поваром на твоей кухне.
2. Разделяй и властвуй
Один агент, который делает всё, работает хуже, чем цепочка специализированных агентов. Каждый фокусируется на своей задаче: один планирует, другой пишет, третий проверяет. Как в настоящей команде разработки.
Но есть ещё одна причина, почему разделение критично. Каждый агент работает в своём контекстном окне. Основной оркестратор не забивается лишней информацией. Агенты меньше галлюцинируют, чётче понимают задачу, работают лаконичнее. Если запихнуть всё в один контекст, качество деградирует с каждым шагом.
3. Мутации как единственная честная проверка
Без мутационного тестирования я не знаю, работают ли мои тесты. Тест может проходить просто потому, что он ничего толком не проверяет. Это прежде всего уверенность в качестве тестов. Мне достаточно прочитать описания тест-кейсов и посмотреть код теста без детального его изучения.
4. Итерации важнее первого результата
Пайплайн рассчитан на то, что с первого раза будет не идеально. Ревью с порогом 9/10 и мутации с 100% catch rate, это встроенные петли обратной связи.
Текущий статус
Пайплайн отлаживался на наборе эталонных компонентов: простой (кнопка), средний (форма с валидацией) и сложный (с бизнес-логикой и стором). Порядка 10 итераций на простой, столько же на средний, и так до тех пор, пока пайплайн не начал выдавать стабильный результат.
Полная миграция не завершена, Enzyme до сих пор живёт в проекте. Мы не выделяли отдельное время на "переписать всё". Вместо этого миграция идёт в процессе работы: тронул компонент, переписал заодно тесты. 40+ компонентов уже прошли через пайплайн на проде.
Знаете, что мне нравится больше всего? Жить в это время. Не именно пока агенты пишет тесты, а вообще сейчас, когда технологии развиваются так стремительно. Играться с такими штуками. Когда я только начинал учиться веб-разработке, я и представить не мог, что буду строить мультиагентные системы для генерации тестов. Что AI будет писать код, ревьюить его и намеренно ломать, чтобы проверить качество. Мир сильно изменился, и мне это нравится.
Когда миграция будет полностью завершена, я напишу третью статью. Посчитаем математику: сколько заняло времени, какой итоговый процент покрытия, сколько стоило в токенах. Цифры, результаты. До скорого!
