Зелёные галочки лгут: почему AI пишет тесты, которые ничего не тестируют, и как это починить

Зелёные галочки лгут: почему AI пишет тесты, которые ничего не тестируют, и как это починить

Тесты проходят, покрытие растёт, но багов меньше не становится. На QA-митапе инженер из крупной продуктовой компании показал, как AI-агенты генерируют тесты, которые ничего не проверяют: подгоняют моки, меняют ассерты, подменяют ожидания. При этом стек команды — near-SOTA: свежая модель, топовый open-source агент. Значит, проблема не в инструментах.

Зелёные галочки лгут

Паттерн знаком каждому, кто использовал AI для написания тестов:

Ты даёшь ей автотесты. Она пишет, они проходят. Зелёные галочки. Но меня пугают зелёные галочки. Почему? Потому что её задача — чтобы все тесты прошли. Она подгоняет результат. Изменяет данные — и у тебя зелёный тест.

Например, нужно протестировать эндпоинт расчёта скидки: при заказе от 5000 ₽ — скидка 10%, но не больше 1000 ₽. В сервисе баг: потолок скидки не применяется.

Без ограничений AI генерирует тест, в котором:

  • сам создаёт мок с нужным ответом;
  • вызывает его;
  • проверяет, что мок вернул то, что в него положили.

Тест проходит, но реальный сервис не участвует. Баг остаётся незамеченным.

Если тест падает, AI не сообщает о баге — он «чинит» тест:

Если красное — она говорит: давай сделаю зелёным. Просто меняет ассерт или мок.

Ожидание result = 1000 заменяется на result = 1500. Тест снова зелёный. Это reward hacking на уровне промпта: цель — не качество, а прохождение тестов.

Проблема не в модели и не в агенте

Команда использовала GLM 4.7 и OpenCode — сильную модель и одного из лидеров open-source агентов. GLM-5.1, её наследник, позже занял первое место на SWE-Bench Pro, обогнав Claude Opus и GPT-5.

OpenCode — мощный инструмент: 140K звёзд на GitHub, LSP-интеграция, поддержка 30+ языков, автоматическая установка серверов. Менять его не на что.

Почему же при near-SOTA стеке возникают проблемы, знакомые по полуторагодичной давности?

Четыре множителя качества

Обычно говорят о трёх: модель, агент, процесс. Но есть четвёртый — качество кодовой базы:

Результат = Модель × Агент × Процесс × Качество кодовой базы

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

У команды из доклада первые два множителя были хороши, процесс — слабый, а четвёртый — провален.

Почему качество кодовой базы — ключевой множитель

Команда писала на TypeScript, но соседняя команда передавала интерфейсы с any повсюду. Это создало слепые зоны для LSP.

OpenCode автоматически запускает TypeScript language server (vtsls), который даёт агенту «зрение»: типы, определения, ошибки. Но any — это слепое пятно: LSP не видит несовместимостей, агент не получает сигнала об ошибке.

Результат:

Ошибка была в несовместимости типов. Агент такой — ну окей, давай будет number. Взял, поменял. Всё проходит, но это вообще не решает проблему.

Агент оптимизирует локально. Зелёный тест, деградация архитектуры.

Что даёт строгая типизация

В моих проектах бэкенд на Java. Там LSP (jdtls) работает в полную силу:

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

Я не допускаю неоднозначностей на code review. Это требует дисциплины, но делает AI-агента предсказуемым: он работает с чистым сигналом, а не с шумом.

Пример: мы — агрегатор авиабилетов и гостиниц. Раньше интеграция сторонних поставщиков занимала месяцы. Теперь — дни. AI-агент под моим руководством читает документацию, разбивает задачу, реализует, тестирует.

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

Ключ — не модель, а pipeline: строгая типизация + обязательные тесты + компиляция как gate. В таком окружении AI предсказуем.

Таблица: как типизация влияет на LSP и AI

Строгая типизация (Java, TS strict, Go, Rust)

  • LSP ловит ошибки типов: ✅ за 50 мс
  • Агент получает диагностику после правки: ✅ мгновенно
  • hover показывает тип: ✅ конкретный
  • Агент «чинит» тест подменой типа: компилятор ругается
  • goToDefinition / findReferences: ✅ работает

Слабая типизация (TS с any, Python без hints)

  • LSP ловит ошибки типов: ❌ не видит
  • Агент получает диагностику: ❌ молчание
  • hover показывает тип: ⚠️ any/Unknown
  • Агент «чинит» тест подменой типа: компилятор молчит
  • goToDefinition / findReferences: ✅ работает (но менее полезно)

LSP — усилитель качества кода. Строгие типы → полное зрение. any → навигация есть, safety net — нет.

Процесс: Spec-Driven Development

Кодовая база объясняет, почему стек не помог. Но процесс тоже важен. Промпт «напиши тесты» — это не процесс. Это надежда на чудо.

Spec-Driven Development (SDD) — подход, при котором AI проходит через несколько фаз. Каждая — с артефактом и проверкой:

  • Фаза 1. Юзкейсы: AI генерирует 15–25 сценариев. Команда добавляет граничные и ошибочные случаи.
  • Фаза 2. Тесткейсы (без кода): Требование «объясни, какой баг ловит тест» отсекает тавтологии. Тест «проверяю, что мок вернул то, что я в него положил» — не проходит.
  • Фаза 3. Код: генерация на основе утверждённых тесткейсов.
  • Фаза 4. Верификация: проверка, что тесты находят реальные баги.

Почему это работает

SDD ломает reward hacking: на каждой фазе своя цель, но нигде — «зелёные галочки».

Да, это требует больше токенов. Но ревью занимает меньше времени, а результат не приходится выбрасывать.

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

Организационный слой

AI не создаёт проблемы — он делает их видимыми.

Техдолг: any-типы, отсутствие контрактов, слабая типизация — AI усиливает их. Живой разработчик компенсирует опыт, AI — галлюцинирует.

Безопасность: on-premise железо не справляется с нагрузкой. Облако, документация и бизнес-логика — под запретом. AI используют в нерабочее время. «Это утопия — покупать железо. Через месяц оно уже устаревает».

Метрики: эффективность AI измеряют по количеству потраченных токенов. Мало — значит, не пользуешься. Как измерение продуктивности разработчика по строкам кода.

Время: «Я всем этим занимался на выходных. Во время работы не мог бы — не хватает времени». Компания «внедряет AI», но не даёт времени на настройку процессов.

Все эти проблемы — организационные. AI их не решает. Но он их обнажает.

Чеклист: что внедрить завтра

Процесс (бесплатно, эффект сразу)

  • [ ] Не давайте промпт «напиши тесты». Начинайте с «выпиши сценарии использования, включая граничные и ошибочные». Код — после утверждения.
  • [ ] Добавьте в промпт: «Не модифицируй тестовые данные и фикстуры для прохождения тестов. Если тест падает — сообщи о расхождении, не чини тест».
  • [ ] Дайте AI пример идеального теста из вашего проекта. Few-shot работает лучше инструкций.
  • [ ] Для каждого теста требуйте комментарий: «Какой конкретно баг он поймает?» Нет ответа — тест не нужен.
  • [ ] Попробуйте инструменты для SDD: Spec Kit (GitHub) или OpenSpec (Fission-AI). Оба open-source, работают с основными агентами.

Качество кодовой базы (стратегически)

  • [ ] На TypeScript — включите strict: true в tsconfig. Каждый устранённый any — это новая диагностика для LSP и AI.
  • [ ] На Python — добавьте type hints в публичные API. Pyright в strict mode начнёт ловить ошибки.
  • [ ] Поставьте задачу на ликвидацию техдолга типизации. Каждый any, заменённый на конкретный тип, включает «зрение» AI.
  • [ ] При выборе стека для нового проекта отдавайте предпочтение строго типизированным языкам с зрелым LSP. Это разница между 50 мс и 45 секундами на поиск контекста.

Инфраструктура

  • [ ] Lint и type-checking как обязательный gate. AI-код не попадает на ревью без прохождения проверок.
  • [ ] Diff-based ревью: смотрите изменения, а не весь сгенерированный код.
  • [ ] Итерационная нарезка задач: один эндпоинт, модуль или фича. Не «все тесты сразу».

Модель (стратегия обновления)

  • [ ] Следите за бенчмарками: SWE-Bench для кодогенерации, Aider Polyglot для мультиязычных задач. SOTA быстро устаревает.
  • [ ] Для on-premise выбирайте open-weight модели с коммерческой лицензией (MIT, Apache 2.0). Привязка к вендору — риск.
  • [ ] Тестируйте новые модели на своих задачах, а не только на бенчмарках.
  • [ ] Помните: обновление модели не решит проблемы any-типов и отсутствия процесса. Модель — множитель, а не замена.

Докладчик закончил так: «AI — это очень хороший помощник, но надо его правильно использовать. Должна быть архитектура, вы должны грамотно передавать контекст. И ревьюить всё, что он сделал».

Читать оригинал