Generated Test Quality

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

Три ступени, а не одна проверка

Вопросы у ступеней разные, и ни одна не выводится из предыдущей.

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

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

Замер на трёх языках: сопровождение, а не только генерация

Отдельная работа ставит задачу шире обычной «сгенерируй тест к функции»: оцениваются три сценария сопровождения набора — создание, починка и обновление, на уровне файла тестов, а не функции, с доступом к контексту всего репозитория. Корпус — 1 539 автоматически извлечённых и проверенных сценариев на Python, Java и Go; протокол оценки безэталонный и опирается ровно на три перечисленных сигнала.

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

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

Суррогат вместо запуска: где проходит граница

Компиляция и прогон стоят дорого, поэтому логично захотеть предсказывать исход, не запуская. Такая модель существует: она предсказывает по исходному коду и коду теста все три сигнала сразу — соберётся ли и отработает ли набор, вырастет ли покрытие, вырастет ли доля убитых мутантов. Обученная на многоязычном корпусе (Java, Python, Go), она даёт средний F1 около 0,69 по трём целям при кратно меньшей задержке и стоимости инфраструктуры, чем цикл «собрать и выполнить».

Из этой цифры следует граница применения, и она жёсткая. При F1 порядка 0,69 примерно треть вердиктов неверна — значит, суррогат не может стоять гейтом: гейт, ошибающийся в трети случаев, либо пропускает негодный набор, либо блокирует годный, и оба исхода молчаливы. Зато он законен перед исполнением: сгенерировать N кандидатов, оценить дёшево, исполнить top-K по-настоящему. Роль меняется с «решает» на «упорядочивает», и цена ошибки падает до потерянного места в очереди.

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

Мутационная логика заходит в оценку фронтира

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

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

Слепое пятно, которое не закрывает ни одна ступень

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

Отсюда требование к источнику ожиданий: тесты, порождаемые от спецификации, а не от готового кода, — единственное, что отделяет проверку от протокола наблюдений. Это же смыкается с Generator Evaluator: оценщик обязан опираться на что-то, чего не производил генератор.

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

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

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

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

Закрывается это не проверкой, а соединением: матрица трассируемости «пронумерованное требование ↔ пометка в коде ↔ метка на тесте». Требование, у которого нет пары ни там, ни там, находится запросом, а не чтением, — то есть это машинный гейт, а не ещё один повод посмотреть глазами.

Предусловие у матрицы жёсткое: требования должны быть пронумерованы и атомарны. Если требование — абзац, то «реализовано частично» становится его нормальным состоянием, и соединение перестаёт что-либо различать: пометка есть, а покрыта ли ею вся формулировка, матрица не знает. Отсюда обязательный шаг на входе — разбор самих требований отдельным агентом по кодифицированному чек-листу до того, как что-либо порождается: в разобранном случае двенадцать критичных замечаний превратили документ в двадцать восемь атомарных требований (AI Native SDLC).

Две оси вместе дают то, чего не даёт ни одна: полнота отвечает, всё ли проверяется, мутация — стоит ли что-нибудь эта проверка. Порядок между ними естественный: сначала полнота, потому что усиливать тесты у половины требований — работа не с той стороны.

Цена и почему она определяет форму

Честный мутационный анализ на многоязычном корпусе — операция часов и суток, а не минут; именно эта цена и породила суррогатные модели предсказания. Направление подтверждается первоисточником (кратно меньшая задержка и стоимость инфраструктуры как заявленная мотивация работы), конкретные величины — из доклада авторов и здесь не проверялись.

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

Связано с

  • Generator Evaluator — асимметрия «проверить дешевле, чем создать» и её граница: у сопровождаемости быстрого оракула нет
  • Verifiable Eval — проверяемая оценка вместо суждения судьи
  • Agent Evals — эталонный набор и гейт в CI, куда встраивается мутационная проба
  • Benchmarks Agents — чем офлайн-бенчмарк отличается от замера на своих данных
  • Agent First Repository — деградация кодовой базы, которую этот слой проверок призван ловить
  • AI Native SDLC — разбор требований до порождения кода: предусловие, без которого матрица трассируемости не различает состояний