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

Агент пишет патч, пишет для него тесты, выполняет их и сообщает об успехе. Дифф выглядит здраво. Итак, у нас есть реализация, файл с тестом, и проверка пройдена (зелёная).
Реализация может согласовываться с тестами, поскольку там содержатся одинаковые ошибки. Если агент недопонял требования, когда писал код, то такое непонимание может сохраниться и на этапе чтения кода в стремлении получить ожидаемый результат. Чем больше тестовый набор, тем сложнее будет устранить такую ошибку. Позже вы исправите реализацию, и тесты начнут ругаться.
Я бы хотел, чтобы программирующий агент выдавал мне более информативный отчёт по задаче. Покажи мне поведение, от которого страхует этот тест. Покажи вероятную неисправную версию, которую этот тест отклонит. Затем покажи, откуда поступает ожидаемый ответ. Последнее не менее важно, чем демонстрация возможности провалить тест.
Две ветки, один пропущенный баг
Возьмём специально подобранный маленький пример. Если заказ стоит менее 5000 центов, то его доставка стоит 500 центов, а если заказ стоит 5000 центов и более, то доставка бесплатная. Сумма заказа — это неотрицательное целое число.
def shipping_fee(total_cents): if total_cents >= 5000: return 0 return 500
В этих тестах выполняются обе ветки кода:
assert shipping_fee(4999) == 500 assert shipping_fee(5001) == 0
Теперь меняем >= на >. Оба теста по-прежнему проходят. То есть, для клиента, который закажет ровно на 5 000 центов, доставка будет платной — в нарушение политики. Тесты дошли до кода, но не различили два правила.
Чтобы ухватить эту разницу, нужно добавить следующее утверждение:
assert shipping_fee(5000) == 0
При правильной реализации тест проходит, а при изменённой — нет. Можно указать на требование и объяснить, почему такой отказ важен.
В следующем исполняемом примере также опробуются два других варианта отказа: случаи, в которых доставка всегда тарифицируется и никогда не тарифицируется. В оригинальных примерах отвергаются два из трёх выбранных нами отказов. Чтобы отвергался и третий, нужно добавить граничное условие. Эквивалентный переписанный код по-прежнему проходит.
Это искусственный пример, не отражающий сеанса работы с настоящей моделью. Три отловленных варианта отказа и есть три отловленных варианта отказа. Они ничего не сообщают нам о баге при заказе, который выходит за рамки этих случаев.
Теперь можно назвать конкретную ошибку, от которой страхуют эти тесты. Таким образом, ревьюеру будет что рассмотреть, даже если тест выполнится успешно.
Если читать код, это может помешать чистоте эксперимента
Эту проблему непосредственно исследовали. В июле 2026 года вышел препринт, в котором Майкл Константиноу, Флориан Тамбон и Майк Пападакис сравнили, как генерируются тесты в пяти разных моделях, а также проверили их на трёх бенчмарках на Python.
Они генерировали реализации, сохраняли те, которые получались некорректными, но при этом исполняемыми, отфильтровывали простые отказы и выбирали по одной сложной отказывающей реализации на каждую задачу. Тесты генерировались в ходе диалога с моделью, при этом модели предлагалась дефектная реализация, и модель обнаруживала всего около 14% отказов. Тесты, сгенерированные из свежего контекста на основе описания задачи, отлавливали около 25%. Чтобы обнаружить ошибку, требовалось отклонить дефектную реализацию, а справочное решение принять.
Эти проценты отражают работу отдельных слабых программ, работающих с бенчмарками. Они не дают представления о том, как часто агент ломает приложение. Но этот результат всё-таки подкрепляет некоторые практические соображения. Если показать генератору тестов реализацию, то мы ожидаемо получим от него ответы, которые продолжают допущенную в реализации ошибку.
Хуанг с коллегами обнаружили, что контекст с корректной реализацией помогает генерации тестов, если модель сравнивает его с некорректным кодом. Читая код, можно извлечь полезную информацию. Только не нужно трактовать поведение модели как готовый ответ, не убедившись, что она действительно ведёт себя правильно.
Свежий контекст помогает разделить работу. Он не уточняет размытое требование, и вторая модель может вновь неправильно понять то же самое требование. Здесь полезно задаться вопросом, откуда берётся ожидаемое поведение, а не смотреть на то, многие ли агенты на нём сходятся.
Тестировать тесты — старая работа
Элементарный пример мутационного тестирования — изменить оператор сравнения. То есть, преднамеренно изменить программу, прогнать тесты, посмотреть, отловят ли они изменение. Удалённого мутанта принято считать убитым. Выживший мутант — повод подробнее изучить тесты, само изменение или и то, и другое.
Эта идея на десятилетия предвосхитила появление программирующих агентов. Ричард ДеМильо, Ричард Липтон и Фредерик Сэйуорд описали этот подход в 1978 году в своей статье об отборе тестовых данных. Агенты для программирования дают ещё один повод ею воспользоваться.
В Meta этот подход уже сочетают с использованием больших языковых моделей. Система ACH от Meta начинает работу, исходя из инженерной проблемы, генерирует имитации отказов, ускользающие от имеющихся тестов, после чего приказывает модели написать тест, который будет проходить на оригинальном коде, а на изменённом — не будет. Далее система отфильтровывает предложенные изменения, а получившиеся в результате тесты отправляет на ревью.
При выполненной Meta оценке Android Kotlin 277 из 571 «антимутантных» тестов (около 49%) не добавили ни строки тестового покрытия. Они находили в коде различия, уже охваченные тестами. Фильтр, требующий покрытия новой строки, отбрасывал бы эти тесты.
ACH усиливает уже существующее поведение на материале избранных сымитированных регрессий. Система не устанавливает, что существующее поведение удовлетворяет всем требованиям. Но, всё-таки, для сгенерированных тестов есть конкретное применение: ревьюер может сам проверить отказ, отлавливаемый каждым предложенным тестом.
Прикрепляем пример к рабочей нагрузке агента. Я сам могу просмотреть сразу и правило, и отказ, и формулировку теста. Список новых названий тестов гораздо менее информативен.
Иногда задание заключается именно в том, чтобы сохранить имеющееся поведение. Набор регрессионных тестов позволяет получить работающее приложение в ходе рефакторинга, даже если никто не предоставил его полную спецификацию. TestGen-LLM от Meta так и работает: отфильтровывает тесты, которые собираются, неоднократно проходят и добавляют покрытие. Ошибочно будет трактовать сохраняющееся поведение как доказательство, что новое правило бизнес-логики реализовано правильно.
Ответ может быть в корне неверным
Вернёмся к приведённому выше примеру с доставкой и изменим граничное условие:
assert shipping_fee(5000) == 500
Теперь правильная реализация не проходит, а ошибочная — проходит. Тест специфичный. Он выполняет релевантную ветку кода. При этом он же требует неправильного ответа.
Агент, добивающийся зелёного прогона теста, может изменить код так, чтобы он удовлетворял этому тесту. Агент, исходно работающий с неисправным кодом, может сгенерировать его и скопировать то, что код уже делает. В любом случае, в зелёном варианте баг с доставкой будет закреплён. Когда основанный на требованиях тест не проходит, сначала исследуйте, почему это произошло, и только после этого меняйте ожидаемый ответ. В противном случае агент может выполнить задачу, просто удалив материал, противоречащий тому патчу, который агент предлагает.
Это проблема тестового оракула. Оракул решает, корректен ли наблюдаемый результат. В работе Стаатса, Уолена и Хеймдаля, посвящённой основам тестирования, программа, набор тестов и оракул рассматриваются как единое целое. Качество теста зависит от того, как они соотносятся друг с другом. Входные данные, затрагивающие интересный код — это хорошо, но их недостаточно, если код ошибочно считается корректным.
Мутационное тестирование не даёт нам недостающего правила бизнес-логики. У вас может сохраниться неверное граничное условие, и тогда будет сгенерировано множество других изменений, которые ваши тесты будут отклонять. Балл растёт, в то время как клиента продолжают тарифицировать против правил.
В данном случае понятно, откуда берётся ожидаемый ответ. Эта политика содержит порог. В приложении таким источником может быть согласованное требование, правило протокола, ранее подтверждённый баг, независимо проверенный расчёт. Каждое утверждение должно быть обосновано чем-то сверх того вывода, который сейчас генерируется имеющимся кодом.
Если никто не может сказать, должна ли доставка быть бесплатной при заказе на сумму ровно 5000 центов, то этот вопрос нужно решить. Просто сгенерировать утверждение недостаточно.
Отказы также необходимо интерпретировать. Работа Meta от 2026 года посвящена динамически срабатывающим тестам разграничивает тесты, обнаруживающие изменение, и тесты, обнаруживающие непреднамеренное изменение. Разработчики подтвердили, что среди 41 отловленного изменения, о котором поступила информация, истинно положительными были всего 8. Сгенерированный тест, который не проходит, может быть полезным, неверным, либо противоречащим тому, изменению, которое вы запросили.
Пусть дополнительная работа стоит своих денег
Прогонять набор тестов по поводу любого мыслимого изменения — это дорого. Не менее дорого отсматривать бесполезные отказы. Агент может легко нагенерировать вам как первые, так и вторые.
В отчёте о мутационном тестировании, выполненном в Google в 2018 году, описан более избирательный подход. Здесь акцент делается на изменённом коде, подавляются попытки применять мутации в бесперспективных точках, ограничивается количество генерируемых мутантов. Найденные проблемы отображаются в код-ревью, где над ними можно поработать. Авторы статьи считают, что внимание разработчика стоит денег, точно как и время выполнения программы.
Для того, чтобы агент внёс ограниченное изменение, изучите то поведение, которому такое изменение может навредить. Изменение правил доставки подсказывает, что с граничным условием есть проблемы. Изменение в авторизации подсказывает, что была допущена какая-то недопустимая операция. Сначала проверьте суть отказа, а потом радуйтесь тому, сколько тестов написал агент.
Затем я прошу агент написать небольшой тест, результат которого можно проверить:
Требование, подтверждающее ожидаемое поведение.
Тест, проходящий при проверке предложенной реализации.
Вероятный поведенческий отказ, при котором данный тест не пройдёт при релевантном утверждении.
Конкретные результаты выполнения, в том числе, отказы, просочившиеся через тест, и те проверки, которые тест не смог выполнить.
Заведомо ошибочные варианты храните в копиях кода, которые можно расходовать. Синтаксическая ошибка или недостающий импорт ещё не показывают, что данным утверждением можно отловить нарушение правила бизнес-логики. Снова примените этот тест к оригиналу и к изменённой версии, а ожидаемый ответ пусть будет фиксированным.
Также убедитесь, что тест выдерживает, если его корректно переписать. Эквивалентные мутанты меняют код, не меняя релевантного поведения. Если требовать отказа при таких изменениях, то у нас получатся тесты, которые регламентируют детали реализации. Стремление добиться какого-либо целевого балла при мутации не может быть оправданием никуда не годному рефакторингу.
Эти проверки всё равно покрывают только те случаи и отказы, которые вы выбрали сами. Они не заменяют интеграционных тестов, проверок на известные реальные баги или ревью такого требования, которое просто не удалось корректно сформулировать. Описанная выше работа должна охватывать такие варианты поведения, отказ по которым действительно имеет значение.
Агент может помочь всё это написать. Он может подсказать, где может быть отказ, написать тест, выполнить обе версии и показать ревьюеру разницу. Но недопустимо, чтобы соответствие агентского теста агентской же реализации само по себе было причиной верить в эту реализацию.
Если агент утверждает, что тесты проходят, я хочу видеть, какие баги этими тестами предотвращаются.