Зелёные галочки лгут: почему AI пишет тесты, которые ничего не тестируют, и как это починить. ai.. ai. DevOps.. ai. DevOps. glm.. ai. DevOps. glm. llm.. ai. DevOps. glm. llm. lsp.. ai. DevOps. glm. llm. lsp. opencode.. ai. DevOps. glm. llm. lsp. opencode. qa.. ai. DevOps. glm. llm. lsp. opencode. qa. TypeScript.. ai. DevOps. glm. llm. lsp. opencode. qa. TypeScript. автотесты.. ai. DevOps. glm. llm. lsp. opencode. qa. TypeScript. автотесты. искусственный интеллект.. ai. DevOps. glm. llm. lsp. opencode. qa. TypeScript. автотесты. искусственный интеллект. тестирование.. ai. DevOps. glm. llm. lsp. opencode. qa. TypeScript. автотесты. искусственный интеллект. тестирование. Тестирование веб-сервисов.. ai. DevOps. glm. llm. lsp. opencode. qa. TypeScript. автотесты. искусственный интеллект. тестирование. Тестирование веб-сервисов. типизация.. ai. DevOps. glm. llm. lsp. opencode. qa. TypeScript. автотесты. искусственный интеллект. тестирование. Тестирование веб-сервисов. типизация. Управление разработкой.

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

Значит, дело не в инструментах. А в чём именно — разбираю ниже: от кода до процесса и организации.

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

Докладчик описал паттерн, с которым сталкивался каждый, кто просил AI написать тесты:

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

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

Что генерирует AI без ограничений:

const mockDiscountService = {
  calculate: jest.fn().mockReturnValue(500)
};

test('applies 10% discount for orders over 5000', () => {
  const result = mockDiscountService.calculate({ total: 5000 });
  expect(result).toBe(500); // ✅ Зелёный. Мок вернул то, что в него положили.
});

Тест зелёный, потому что AI замкнул цикл: сам придумал мок → сам вызвал мок → сам проверил мок. Реальный сервис не участвует. Баг не найден.

Второй уровень проблемы: когда тест падает на реальном сервисе, AI «чинит» не сервис, а тест:

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

Тест на потолок скидки падает (реальный ответ 1500 вместо 1000). AI вместо того, чтобы сообщить о баге, меняет expect(result).toBe(1000) на expect(result).toBe(1500). Зелёный.

Это reward hacking на уровне промпта: когда вы говорите «напиши тесты», AI интерпретирует задачу как «сделай так, чтобы тесты проходили». Зелёный результат — цель. Всё остальное — средство.

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

Я спросил докладчика, какие модели и агенты они используют. Ответ: GLM 4.7, OpenCode.

На тот момент я прокомментировал: «Эти проблемы были год-полтора назад. С флагманскими моделями и агентами ситуация другая.» Но когда сел писать статью — перепроверил. И вынужден поправиться.

GLM 4.7 вышла в декабре 2025 года. Доклад был в марте 2026. Это три месяца от релиза — никак не устаревшая модель. Более того, её наследник GLM-5.1, вышедший в апреле 2026, занял первое место на SWE-Bench Pro (58.4%), обогнав Claude Opus 4.6 и GPT-5.4. Модель сильнее — но без строгой типизации и процесса упрётся в тот же потолок. LSP по-прежнему не увидит ошибку через any, а промпт «напиши тесты» всё так же спровоцирует reward hacking.

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

Так почему при near-SOTA стеке — проблемы полуторагодичной давности?

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

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

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

Любой множитель близок к нулю — и результат около нуля, хоть остальные будь идеальными. При этом кодовая база задаёт потолок: выше него не прыгнут ни модель, ни процесс. У ребят из доклада первые два множителя были нормальные. Третий (процесс) — слабый: промпт «напиши тесты» без ограничений. Но главный провал — четвёртый.

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

На митапе выплыла деталь, которая объясняет почти всё. Команда пишет на TypeScript, а соседняя команда (разработки) отдаёт интерфейсы с any повсюду. Я тогда сказал:

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

А дальше — чистая механика. OpenCode запускает TypeScript language server (vtsls) автоматически, как только видит .ts-файлы в проекте. LSP-сервер даёт агенту «зрение»: типы, определения, ссылки, диагностики ошибок. Когда агент вносит изменение, ломающее типы, LSP сообщает об ошибке за миллисекунды — и агент чинит её в том же ходе, без запуска компилятора.

Но any — это слепое пятно. Если функция принимает any и возвращает any, TypeScript LSP не видит ошибки при передаче неправильного типа. Агент не получает красного сигнала. И делает ровно то, что описал докладчик:

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

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

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

В моих проектах бэкенд на Java. Строго типизированный язык, где LSP (jdtls) работает в полную силу:

  • Если AI написал чушь — код не скомпилируется. Первый автоматический фильтр.

  • Если AI изменил сигнатуру метода — LSP мгновенно сообщит обо всех вызовах, которые сломались.

  • Если AI передал неправильный тип — ошибка компиляции, а не тихий any.

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

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

Уволенный middle-разработчик бэкенда на 80% заменён AI-агентом. Под моим руководством агент читает чужую документацию, разбивает интеграцию на этапы, реализует, прогоняет тесты. Причём есть сценарий, который для живого разработчика практически невозможен: когда API плохо задокументирован и описанные комбинации параметров не работают, агент перебирает варианты, пока не найдёт рабочий. Человек на это не пойдёт — слишком монотонно.

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

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

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

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

LSP ловит ошибки типов

✅ за 50 мс

❌ не видит

Агент получает диагностику после правки

✅ мгновенно

❌ молчание

hover показывает тип

✅ конкретный

⚠️ any / Unknown

Агент «чинит» тест подменой типа

Компилятор ругается

Компилятор молчит

goToDefinition / findReferences

✅ точно

✅ работает (но менее полезно)

OpenCode запускает LSP-серверы автоматически — от команды не требуется ничего, кроме наличия проекта. Серверы скачиваются и устанавливаются при первом обращении к файлу. Но LSP — усилитель существующего качества кода. Строгие типы → LSP даёт агенту полное зрение. any повсюду → LSP даёт навигацию, но не safety net.

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

Кодовая база объясняет, почему near-SOTA стек не спасает. Но процесс тоже провален. any-типы он не вылечит — зато свой множитель поднимет с нуля до рабочего значения. И это можно сделать уже завтра.

Промпт «напиши тесты» — это не процесс. Это запрос на чудо. Spec-Driven Development (SDD) — подход, когда вместо одного промпта вы проводите AI через несколько фаз. Каждая фаза — свой артефакт, своя проверка.

Фазы SDD

Фаза 1. Юзкейсы.

Проанализируй сервис DiscountService (код ниже). 
Выпиши все сценарии использования, включая:
- Позитивные (основной поток)
- Негативные (невалидные входные данные)
- Граничные (пороговые значения, пустые коллекции, null)
Для каждого сценария: входные данные, ожидаемый результат, предусловия.
Код пока не пиши.

AI выдаёт 15–25 сценариев. Вы смотрите: есть ли граничные случаи, которые вы знаете? Если нет — добавляете.

Фаза 2. Тесткейсы (без кода).

По утверждённым сценариям напиши тесткейсы в формате Given/When/Then.
Для каждого тесткейса укажи: какой конкретно баг он поймает,
если бизнес-логика сломается. 
Если не можешь сформулировать — тесткейс не нужен.
Код пока не пиши.

Требование «объясни, какой баг ловит тест» — фильтр против тавтологических проверок. Тест «проверяю, что мок вернул то, что я в него положил» не проходит этот фильтр, потому что невозможно назвать баг, который он ловит.

Фаза 3. Код.

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

Фаза 4. Верификация.

Пройди по каждому тесту и проверь:
1. Вызывается ли реальная логика сервиса (не замкнут ли тест на мок)?
2. Соответствует ли ассерт тому, что указано в тесткейсе?
3. Поймает ли тест мутацию (если заменить > на >= — упадёт ли)?

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

SDD ломает reward hacking: на каждой фазе — своя цель. На фазе 1 — полный список сценариев. На фазе 2 — формулировка «какой баг ловит тест». На фазе 3 — код, соответствующий тесткейсам. Нигде цель не «зелёные галочки».

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

Докладчик, кстати, пришёл к итерационной нарезке сам: «Я разбиваю задачи по 3–4 штуки, делаю потихонечку.» Нарезка — хорошо, но мало. SDD добавляет то, чего в простой нарезке нет: у каждой фазы своя цель и свой артефакт, фильтр «какой баг ловит тест» отсекает тавтологии, а запрет на правку ассертов блокирует reward hacking.

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

На митапе всплыло ещё кое-что — вещи, которые к AI напрямую не относятся, но определяют весь опыт работы с ним.

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

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

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

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

Все четыре штуки — организационные. AI их не создаёт. Зато делает их видимыми.

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

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

  • [ ] Никогда не давайте промпт «напиши тесты». Начинайте с «выпиши сценарии использования, включая граничные и ошибочные». Код — после утверждения сценариев.

  • [ ] Добавьте в системный промпт: «Не модифицируй тестовые данные и фикстуры для прохождения тестов. Если тест падает — сообщи о расхождении, не чини тест.»

  • [ ] Дайте AI пример идеального теста из вашего проекта. Few-shot работает лучше любых инструкций.

  • [ ] Для каждого теста требуйте комментарий: какой конкретно баг он поймает. Нет ответа — тест не нужен.

  • [ ] Попробуйте готовые инструменты для SDD. Spec Kit (GitHub) проводит агента через четыре фазы: Specify → Plan → Tasks → Implement — туториал. OpenSpec (Fission-AI) превращает спецификацию в живой документ с дельта-маркерами — туториал. Оба open-source, оба работают с основными AI-агентами.

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

  • [ ] Если вы на TypeScript — включите strict: true в tsconfig. Каждый устранённый any — это диагностика, которую LSP начинает видеть, а значит, видит и AI-агент.

  • [ ] Если вы на Python — добавьте type hints хотя бы в публичные API тестируемых сервисов. Pyright (LSP в OpenCode) в strict mode начнёт ловить ошибки, которые сейчас проходят молча.

  • [ ] Поставьте задачу на ликвидацию техдолга типизации. Это не абстрактный совет: каждый any, заменённый на конкретный тип, буквально включает «зрение» AI-агента через LSP.

  • [ ] Если команда выбирает стек для нового проекта — учтите, что строго типизированные языки с зрелым LSP-сервером дают AI-агентам кратно больше контекста. Это разница между 50 мс точного ответа и 45 секунд grep по 800 файлам.

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

  • [ ] Lint и type-checking как обязательный gate. AI-код не попадает на ревью, пока не пройдёт автопроверки.

  • [ ] Diff-based ревью: смотрите изменения, а не весь сгенерированный код.

  • [ ] Итерационная нарезка задач: один эндпоинт, один модуль, одна фича. Не «тесты для всего проекта».

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

  • [ ] Следите за бенчмарками: SWE-Bench для кодогенерации, Aider Polyglot для мультиязычных задач. Сегодняшний SOTA через полгода — середняк, конкретные рекомендации устаревают быстрее, чем выходит статья.

  • [ ] Для on-premise выбирайте open-weight модели с коммерческой лицензией (MIT, Apache 2.0). Привязка к одному вендору обходится дорого — и тем дороже, чем быстрее движется рынок.

  • [ ] Проверяйте новую модель на ваших реальных задачах, а не только на бенчмарках. Модель, которая блестяще решает SWE-Bench, может буксовать на вашем проекте.

  • [ ] Но помните: обновление модели не решит проблему any-типов и отсутствия процесса. Модель — множитель, а не замена остальных факторов.


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


Об авторе. Андрей Ерёменок — CTO, 20+ лет в разработке. Пишу о пересечении AI и инженерных практик. Предыдущие статьи на Хабре. Telegram: Пикник Айтишника.

Автор: aeremenok

Источник