16 дек. 2025 г.·7 мин чтения

ИИ‑сгенерированные тесты, которые разработчики действительно оставят

ИИ‑сгенерированные тесты работают лучше, когда они нацелены на рискованные пути и стабильное поведение, а не на каждую ветку. Узнайте, как увеличить покрытие без хрупкого шума.

ИИ‑сгенерированные тесты, которые разработчики действительно оставят

Почему большинство сгенерированных тестов не выживают

Команды сохраняют тесты, которые ловят дорогие ошибки. Они удаляют тесты, которые кричат «волком».

Вот почему многие сгенерированные тесты исчезают через несколько недель после появления. Модель копирует то, что код делает сейчас, вместо того чтобы проверять, что может навредить потом. Она читает текущий вывод, селекторы, имена классов и вызовы хелперов, а затем превращает этот снимок в утверждения. Результат выглядит занятым, но мало защищает.

Команда может получить 50 новых тестов за один проход и при этом чувствовать себя менее защищённой. Если тесты падают каждый раз при переименовании кнопки, перемещении поля или правке текста страницы, люди перестают доверять набору. Как только доверие падает, начинаются удаления.

UI‑тесты страдают сильнее всего. Небольшое изменение в верстке может сломать большую партию тестов, которые зависят от одних и тех же хрупких деталей. Для пользователя ничего важного не изменилось, но прогон становится красным. После двух‑трёх таких раундов набор тестов начинает казаться тяготой.

Шум хуже, чем низкое покрытие. Короткий набор тестов, который падает при реальных багах, помогает разработчикам двигаться быстро. Большой набор слабыми проверками замедляет ревью, скрывает серьёзные ошибки и приучает игнорировать красные сборки.

Ещё одна проблема — неверно направленные усилия. Сгенерированные тесты часто фокусируются на лёгких путях, потому что такой код проще прочитать. Рискованное поведение обычно сидит в крайних случаях: некорректные входные данные, проверки прав, попытки повторов, правила ценообразования, таймауты или изменения состояния после частичного сбоя. Модели сложнее это вывести, поэтому этим уделяют меньше внимания, хотя это важнее.

Представьте страницу оформления заказа. Модель может написать десять тестов на порядок полей, метки и текст кнопки. Команда не сохранит ни одного из них, если правка текста сломает половину набора. Один тест, который ловит двойное списание после повтора, стоит гораздо больше.

Больше тестов не помогает, когда никто им не верит. Команды оставляют тесты, которые спокойны при обычных изменениях и громко сигналят, когда ломается что‑то важное. Всё остальное — уборка, которая ждёт своего часа.

Начинайте с рискованных путей, а не с голого покрытия

Число покрытия может выглядеть хорошо, хотя продукт всё ещё ломается там, где это дорого. Если поток может неправильно начислять сумму, удалять данные, раскрывать приватные записи или блокировать пользователей, тестируйте это в первую очередь. Здесь сгенерированные тесты действительно приносят пользу.

Большинству команд лучше нацеливаться на части приложения, которые уже вызывают боль в продакшене. Посмотрите заявки в поддержку, заметки об инцидентах, постмортемы и баг‑репорты за последние месяцы. Если одна и та же зона постоянно падает, попросите ИИ помочь с ней прежде, чем генерировать ещё тесты для маленького хелпера, о котором никто не беспокоится.

Первичные цели обычно: логика оплаты и выставления счетов, проверки прав доступа, импорты и экспорты, изменения состояния заказов или запасов и всё, что ломалось в недавних релизах.

Это значит пропустить много низкорискованного кода на старте. Геттер, который возвращает одно поле, маппер с очевидным входом и выходом или тонкая обёртка над библиотекой могут поднять отчёт о покрытии, но редко защищают бизнес. Такие тесты часто становятся шумом — они падают при безвредных рефакторах, и никому не жалко их удалить.

История инцидентов даст вам лучшую карту, чем покрытие. Если логи продакшена показывают, что логика повторов падает при таймаутах, протестируйте путь таймаута. Если импорт CSV постоянно исковеркаляет даты, протестируйте грязные файлы и частичные ошибки. Если пользователи теряют изменения при конфликтах автосохранения, протестируйте правила конфликтов.

Шаблон прост: следуйте туда, где ошибки стоят денег, доверия или часов уборки.

Именно так практичные команды используют ИИ для тестирования. Вам не нужны 50 новых тестов по безопасному коду. Вам нужно несколько вокруг тех частей, которые не дают спать по ночам.

Выбирайте стабильные поведения, которые команда хочет сохранить

Команды оставляют тесты, когда они защищают решения, которые продукт уже принял. Удаляют тесты, которые жалуются на безвредные изменения. Для сгенерированных тестов это ещё важнее, потому что модель с радостью породит много шума, если ей позволить.

Начните с бизнес‑правил, а не с деталей интерфейса. Правило вроде «пользователи на триале не могут экспортировать данные» или «заказы выше определённой суммы требуют ручной проверки» стоит тестировать, потому что команда хочет, чтобы это поведение оставалось постоянным. Метка кнопки, CSS‑класс или точный порядок разметки обычно — нет.

Хорошие цели имеют долгий срок жизни. Правила доступа, расчёты цен и налогов, изменения состояния после действия, контракты API и побочные эффекты вроде отправки одного счёта или создания одной записи, как правило, переживают рефакторинг.

Что стоит избегать

Многие сгенерированные тесты падают по неправильной причине. Они цепляются за текстовые метки, структуру DOM, приватные имена функций или точные метки времени до секунды. Эти детали меняются в процессе обычной работы, поэтому набор тестов начинает раздражать вместо того, чтобы помогать.

Если время важно, проверяйте диапазон или формат. Если UI важен, проверьте, что действие работает, а не что имя класса осталось идентичным навсегда. Небольшие изменения не должны создавать лишнюю работу для команды.

Проверки контрактов обычно безопаснее, чем проверки реализации. Если API должен вернуть total, currency и payment status, протестируйте этот контракт. Не проверяйте, какой хелпер собрал ответ или сколько внутренних вызовов произошло. Разработчики должны иметь возможность переписать внутренности без переписывания половины набора тестов.

Простое правило: если разработчик может сильно изменить код, а пользователь должен увидеть тот же результат — тест должен проходить. Если тест ломается, потому что «Buy» поменялось на «Purchase», он вряд ли защищал что‑то важное.

Простой процесс для запроса тестов у ИИ

Просьба «полный набор тестов» обычно приводит к заполнению барахлом. Вы получите тесты, которые повторяют код, фиксируют мелкие детали UI или ломаются после безвредного рефактора. Лучше держать запрос узким и направленным на поведение, которое вы действительно хотите защитить.

Начните с одного пользовательского потока. Сделайте его конкретным: «пользователь применяет купон при оформлении заказа» или «админ экспортирует отчёт». Затем добавьте один режим ошибки, который был бы критичен, например просроченный купон или таймаут от платёжного сервиса.

Далее опишите ожидаемое поведение простым языком. Избегайте тестового жаргона. Подойдёт одно предложение: «Если купон просрочен, приложение показывает ошибку и сохраняет исходную сумму.» Это даёт модели цель, которая соответствует реальному поведению продукта, а не структуре кода.

Полезный шаблон запроса такой:

  1. Назовите один поток, важный для пользователей.
  2. Назовите один способ, как этот поток может сломаться.
  3. Вставьте только код, нужный для понимания этого поведения.
  4. Попросите 2–4 сфокусированных теста.
  5. Просмотрите результат и быстро удалите слабые тесты.

Контекст важнее, чем выбранная модель. Вставьте функцию, компонент или обработчик под тест и небольшие хелперы, от которых он зависит. Не сливайте всё дерево файлов. Слишком много контекста толкает модель к широким и шумным догадкам.

Просите несколько тестов, а не «покрытие для галочки». Один хороший тест на счастливый путь и один‑два теста на ошибки лучше пятнадцати поверхностных проверок. Если вы хотите проверять стабильное поведение, скажите это прямо: попросите модель утверждать видимые результаты, возвращаемые значения, сохранённые записи или эмитируемые события. Попросите не проверять приватные вызовы, точную разметку и детали, которые вы ожидаете менять.

Затем ревьюируйте жёстко. Сохраняйте тесты, которые поймают реальный баг. Удаляйте тесты, которые только подтверждают работу моков, зеркалят реализацию построчно или ломаются при переименовании переменной.

Короткий запрос часто лучше сложного. Чёткая область, понятное ожидаемое поведение и маленький кусок кода обычно дают тесты, которые команда сохранит.

Реалистичный пример: один поток checkout

Исправить шумную тестовую базу
Обрежьте слабые проверки и сохраните тесты, которые действительно ловят баги.

Поток оформления заказа — хорошее место, чтобы увидеть, какими должны быть полезные сгенерированные тесты. Там деньги, изменения состояния и пути отказа, которые могут быстро навредить клиентам. Потому это лучшая цель, чем статическая страница настроек или экран с мелкими деталями UI.

Возьмём простой поток оплаты картой. Покупатель вводит данные карты, нажимает «Оплатить», и приложение создаёт заказ только если платёжный провайдер одобрил списание. Тесты, которые заслуживают место в проекте, фокусируются на том, что должно оставаться верным даже после рефакторинга.

Один тест должен покрывать счастливый путь. После успешного списания заказ должен перейти в состояние paid, корзина должна очиститься, и клиент должен увидеть подтверждение, связанное с этим заказом. Эти проверки важны, потому что описывают бизнес‑результат, а не оформление страницы.

Второй тест должен покрывать отклонённую карту. В этом случае заказ должен остаться неоплаченным или перейти в состояние failed, и система не должна создавать второй заказ в фоне. Если текст ошибки изменится позже — обычно это нормально. Если состояние заказа неверно — это проблема.

Третий тест должен покрывать дублированную отправку. Люди дважды кликают. Браузеры ретрают. Мобильные сети подвисают. Если покупатель нажимает «Оплатить» дважды, система всё равно должна создать один заказ и списать сумму один раз. Это именно тот рискованный путь, про который команды забывают, пока не накопятся заявки в поддержку.

Обратите внимание, чего эти тесты не проверяют. Они не заботятся о цвете кнопки, точных отступах или полном снимке страницы. Снэпшот‑тесты всей страницы часто ломаются, когда кто‑то чуть поменял копию или сдвинул поле на несколько пикселей. Такой шум быстро накапливается.

Налоговые правила — отдельный случай. Если ваша логика налогов редко меняется, оставьте один сфокусированный тест для неё. Например: налогооблагаемый товар, отправленный в конкретный регион, должен привести к ожидаемой сумме налога в сохранённом заказе. Это даёт покрытие там, где ошибки стоят денег, без наполнения набора хрупкими проверками ради красивой метрики покрытия.

Куда сгенерированные тесты обычно ошибаются

Большинство плохих сгенерированных тестов пытаются доказать слишком много и доказывают неправильное. Тест, проверяющий каждое поле, CSS‑класс, хелпер‑вызов и метку времени, может выглядеть тщательным, но ломается при рефакторинге без вреда для пользователя.

Первый плохой паттерн — чрезмерные утверждения. Если важен рефанд потому, что клиент получает деньги назад и статус заказа меняется, тест должен фокусироваться на этом результате. Нет нужды фиксировать каждое промежуточное значение, если оно не влияет на бизнес‑правило.

Ещё одна типичная ошибка — чрезмерный мокинг. ИИ часто мока́ет базу, очередь, платёжный клиент, часы и половину приложения вокруг них. Тест зелёный, но он лишь доказывает, что моки возвращают ожидаемые моки.

Такие тесты редко ловят баги, которые важны команде. Они обходят реальные грани, где код, данные и внешние сервисы встречаются.

ИИ также привязывается к текущей структуре проекта. Он читает имена хелперов, пути файлов и внутренние обёртки, а затем пишет тесты, зависящие от них. Переименуй хелпер или перемести модуль — и несколько тестов упадут, хотя поведение осталось прежним.

Ещё одна ловушка — объём. Когда модель находит один рискованный путь, она часто создаёт четыре дополнительных теста, которые отличаются одной строкой ввода или флагом. Вы платите за ревью пять раз, а покрытие почти не растёт.

Тихая проблема — флейковое состояние. Сгенерированные тесты забывают очистку, переиспользуют общие данные или оставляют фоновые задания, временные файлы и строки в базе. Набор зелёный на одной машине и падает в CI на утро.

Тест, скорее всего, идёт в неверном направлении, если он больше заботится о вызовах хелперов, чем о результатах для пользователя, заменяет почти все зависимости мока́ми, ломается после безвредного рефактора, добавляет шум без покрытия реального риска или оставляет данные после выполнения.

Хорошие сгенерированные тесты немного скучны. Они проверяют одно важное поведение, используют мало мока́в, переживают рефакторинг и оставляют систему чистой для следующего теста.

Как ревьюить сгенерированный тест перед сохранением

Построить практичные AI-воркфлоу
Постройте рабочие процессы по программированию, ревью и тестированию, которые сохраняют полезность вывода AI.

Тест заслуживает место, когда он поймает баг, о котором вы позаботитесь через месяц, а не когда он доказывает, что все метки, имена классов и пиксели остались прежними.

Начните с одного грубого вопроса: какой баг поймает этот тест? Если вы не можете ответить одним предложением, тест, вероятно, шум. Хорошие ответы звучат как реальные сбои: «пользователя спишут дважды» или «просроченный токен всё ещё открывает страницу аккаунта».

Этот вопрос помогает быстро убрать слабые утверждения. Если утверждение зависит от формулировки кнопки, раскладки страницы или порядка неважных элементов — удалите его, если только это не часть правила, которое вы хотите сохранить. Текст и верстка меняются часто. Бизнес‑поведение должно меняться реже.

Имя теста важнее, чем многие признаются. «renders checkout screen» мало что говорит. «blocks payment when card is expired» — сразу объясняет правило, которое тест защищает. При падении в CI такое имя экономит время.

Сгенерированные тесты любят повторяться. Они создают слишком много setup, генерируют данные, которые вообще не используются в утверждениях, и трижды повторяют одну проверку. Обрежьте жёстко. Если два хелпера делают одно и то же, оставьте один. Если три утверждения доказывают одно и то же, оставьте самое понятное.

Быстрое ревью — это пять проверок:

  • Тест указывает на один баг или одно бизнес‑правило.
  • Утверждения избегают хрупкого текста и деталей разметки.
  • Setup содержит только данные, нужные для сценария.
  • Имя теста объясняет правило простым языком.
  • Тест проходит дважды подряд на вашей машине.

Последний шаг ловит больше проблем, чем ожидают. Запустите тест дважды, а если он затрагивает время, случайности, сетевые моки или асинхронный UI — несколько раз. Флейки по таймингу часто проявляются быстро, когда сгенерированный код полагается на случайности.

Если команда регулярно пользуется сгенерированными тестами, такая привычка ревью сохраняет набор компактным. В результате у вас будет меньше тестов, но они будут падать по причинам, которые действительно важны.

Короткий чек‑лист перед добавлением тестов в CI

Получить fractional CTO
Привлечь старшего технического лидера для тестирования, архитектуры и внедрения AI без найма на полный рабочий день.

Сгенерированный тест должен заслужить место. Как только он начинает запускаться в CI, он перестаёт быть экспериментом и начинает отнимать время у команды при каждом падении. Если он не защищает то, что может повредить пользователям, деньгам или доверию, ему там не место.

Быстрая проверка перед сохранением любого ИИ‑теста:

  • Привяжите тест к реальной ошибке. Спросите: «Какой сбой поймает этот тест?» Если никому не важно это падение — удаляйте тест.
  • Предпочитайте результаты, которые переживут рефакторинг. Проверяйте суммы, сохранённые записи, правила доступа или видимые результаты для пользователя. Избегайте приватных вызовов, точного числа моков или хрупкой разметки.
  • Читайте тест как новый участник команды. Он должен объяснять setup, действие и ожидаемый результат с первого взгляда. Если для понимания нужен исходный запрос к ИИ — перепишите.
  • Убедитесь, что тест падает по одной ясной причине. Тест, который проверяет три вещи одновременно, превращает любое падение в расследование.
  • Оцените сопровождение. Если тест зависит от странных фикстур, хитростей с промптами или набора мока́в, понятных только одному человеку, он быстро сгниёт.

Здесь многие команды бывают слишком снисходительны. ИИ пишет десять тестов, семь проходят, и люди сохраняют все семь. Это кажется продуктивным один день. Через месяц набор замедляет ревью, и никто не доверяет падениям.

Небольшой пример делает это конкретным. Если тест проверяет поток checkout, оставьте тест, который доказывает, что система отклоняет дублирующее списание или правильно считает налоги. Удалите тест, который фиксирует точный порядок внутренних вызовов, если этот порядок не является обещанным поведением.

Хорошие CI‑тесты читаются как простая инструкция. Кто‑то должен открыть файл и понять, какое действие пользователя покрывается, какой результат важен и зачем тест существуeт. Если вы не можете объяснить это одним предложением, тест ещё черновик.

Лучший тест для CI часто меньше, чем первая версия, которую сгенерировал ИИ.

Что делать дальше

Начните с малого. В следующем спринте выберите 3–5 потоков, где ошибка стоила бы времени или денег. Это может быть вход, checkout, генерация счёта, проверки прав или API‑путь, который ломает другие команды при изменениях.

Это работает лучше, чем приказ «повышай покрытие везде». Сгенерированные тесты заслуживают доверия, когда они наблюдают за рискованными путями и стабильным поведением, а не за каждой веткой, которую модель найдёт.

Напишите короткое командное правило перед тем, как кто‑то добавит ещё тестов. Держите его таким, чтобы его можно было прочитать за минуту. ИИ может тестировать поведение, видимое пользователю, бизнес‑правила и баги, которые уже причиняли боль. Ему не место в приватных хелперах, тайминго‑чувствительных внутренних механизмах и верстке, которая меняется каждую неделю. Каждый сгенерированный тест должен иметь человеческого владельца. Если никто не будет чинить тест после рефакторинга — не мержьте его.

Затем отслеживайте три показателя в течение месяца: сколько сгенерированных тестов вы удалили, сколько стало флейковыми и сколько поймало реальный баг до релиза. Эти числа скажут больше, чем сырое покрытие.

Простой пример делает это наглядным. Если команда добавила десять ИИ‑тестов для потока checkout и удалила шесть из них за две недели, значит промпт или правило ревью неверны. Если два теста остались стабильными и поймали налоговые или платёжные кейсы — вы на верном пути.

Делайте ревью там же, где вы ревьюите код. Обращайтесь с сгенерированными тестами как с кодом, но дайте им меньше доверия сначала. Тест, который выглядит остроумно, но ломается каждую пятницу, — лишний груз.

Если команде нужна помощь в формализации правил, Oleg Sotnikov at oleg.is работает со стартапами и малыми компаниями над практическим AI‑первым процессом разработки и внедрения. Внешний ревью может помочь, когда команда хочет быстрый результат без превращения набора тестов в шум.

Следующий шаг намеренно скучный: выберите несколько рискованных потоков, напишите короткое правило и измерьте, что выжило. Через месяц вы поймёте, стала ли ваша стратегия тестирования острее или просто больше.

Часто задаваемые вопросы

Почему ИИ‑сгенерированные тесты так часто удаляют?

Они часто привязываются к тексту, селекторам и внутренним вызовам вместо рискованного поведения. После нормального рефактора такие тесты ломаются, тестовая база «кричит волком», и команда перестаёт доверять результатам.

С чего лучше начать тестирование с помощью ИИ?

Начните с потоков, где ошибка стоит денег, доверия или часов ручной доработки, например checkout, биллинг, права доступа, импорт/экспорт или вход в систему. Заявки в поддержку, баг‑репорты и разборы инцидентов подскажут приоритеты.

Стоит ли гоняться за покрытием для сгенерированных тестов?

Пока нет. В начале лучше небольшая база, покрывающая рискованные сценарии, чем большое число слабых проверок. Команда работает быстрее, когда тесты падают из‑за реальных багов, а не из‑за безвредных изменений.

Что делает сгенерированный тест стабильным?

Стабильные тесты проверяют бизнес‑правила и результаты для пользователя, которые должны сохраняться при рефакторинге: отправлен один счёт, создан один заказ, доступ запрещён для неправильной роли. Они избегают текста кнопок, имён классов, точной разметки и частных вспомогательных функций.

Как правильно просить ИИ писать тесты?

Назовите один поток, одну ошибку, опишите ожидаемое поведение простым языком и дайте только нужный код. Попросите 2–4 сфокусированных теста и требуйте проверок видимых результатов, а не внутренних вызовов.

Сколько мока́в — это слишком много?

Если тест заменяет половину приложения мока́ми, он, вероятно, мало доказывает. Мокайте только то, что необходимо, а утверждения делайте по сохранённым данным, возвращаемым значениям и результатам для пользователя.

Как выглядит хороший тест для checkout?

Для checkout оставьте тесты на успешный платёж, отклонённую карту и защиту от дублированной отправки. Эти проверки ловят дорогие баги, не привязывая набор тестов к тексту или верстке.

Как проверять сгенерированный тест перед его слиянием?

Спросите одно простое: какой баг поймает этот тест? Если нельзя ответить в одно предложение, тест — шум. Обрежьте ненужную настройку, удалите хрупкие утверждения и запустите тест дважды, чтобы поймать флейки по таймингу или очистке.

Нужно ли отправлять в CI каждый прошедший ИИ‑тест?

Нет. В CI должны попадать только тесты, которые команда будет исправлять в следующем месяце. Если тест требует редких фикстур, множества мока́в или ломается при безвредном рефакторе, перепишите или не добавляйте его.

Как понять, что наша стратегия ИИ‑тестов работает?

Отслеживайте, что вы удаляете, что становится флейковым и что ловит реальные баги до релиза. Эти числа покажут, растёт ли полезная база тестов или просто её объём.