TL;DR
LLM и кодинг-агенты пишут тесты с моками так, что те запускаются и проходят. Но проходящий тест не значит верный. Мок — это подставной объект вместо внешнего сервиса (банк, база, API). Для него нужны три вещи: что подменить, что подмена возвращает (заглушка, stub) и как проверить, что её вызвали правильно (проверки вызовов, mock assertions). Агент с доступом к проекту и запуску тестов чаще выдаёт код, который собирается и проходит. Но заглушки и проверки вызовов у него получаются не точнее, чем у обычной LLM в чате.
Боль в том, что зелёный тест создаёт иллюзию надёжности. Модель легко угадывает, какие зависимости подменить. Хуже даёт что именно они возвращают и с какими аргументами и сколько раз их должны вызывать. Тест проходит, но проверяет не то. Пример из статьи: баг, который делает лишний третий вызов банка, проходит обычную проверку результата и ловится только проверкой «вызвали ровно два раза». Агент запускает тесты и подгоняет код под зелёный результат. Поэтому растёт исполнимость, а не точность. Дополнительное время работы агента почти ничего не меняет: исполнимость растёт слабо, точность вообще не растёт.
Практический вывод: не принимай «тесты прошли» как доказательство качества мок-тестов. Отдельно проверяй возвращаемые значения заглушек и проверки вызовов (аргументы, счётчики, порядок).
Схема эксперимента (как устроена проверка)
ШАГ 1: Берут рабочий тест разработчика с моками (эталон)
ШАГ 2: Вырезают из него три артефакта → мок-объекты, заглушки, проверки вызовов
ШАГ 3: Модели/агенту дают «пустой» тест + тестируемый метод → просят восстановить все три части разом
ШАГ 4: Вставляют результат обратно в тест и проверяют по трём осям:
• исполнимость — компилируется и проходит?
• верность — совпадает ли с эталоном по смыслу (что вернула заглушка, с какими аргументами вызвали)?
• ловит ли баги — обнаруживает ли искусственно внесённые ошибки во взаимодействии с зависимостью?
Пример применения
Задача: Бэкенд-разработчик интернет-магазина просит агента (Claude Code или Cursor) написать юнит-тесты для метода оплаты через ЮKassa. Метод делает до трёх попыток при таймауте. Агент отчитывается: «все тесты проходят». Разработчик хочет понять, не проверяют ли эти тесты пустоту.
Промпт:
Ты пишешь юнит-тесты на pytest + pytest-mock для метода PaymentService.charge(card_id, amount).
Метод вызывает yookassa_client.authorize() до 3 раз и повторяет попытку только при TimeoutError.
Тебе нужно сгенерировать три вещи, и каждую отдельно подписать комментарием:
1. МОК-ОБЪЕКТЫ — какие зависимости подменяем и почему именно их.
2. ЗАГЛУШКИ (stub) — что возвращает/выбрасывает каждая подмена в каждом сценарии
(сценарии: успех с первой попытки; таймаут → успех; три таймаута подряд; отказ банка).
3. ПРОВЕРКИ ВЫЗОВОВ (mock assertions) — для каждого сценария: сколько раз вызвали authorize(),
с какими точными аргументами, в каком порядке. Одной проверки результата charge() недостаточно.
После кода выведи таблицу: сценарий | что вернула заглушка | ожидаемое число вызовов | почему именно столько.
Не подгоняй ожидания под текущую реализацию — выводи их из описанного поведения метода.
Результат: Модель выдаст тестовый файл с тремя явно разделёнными блоками и таблицей сценариев. По таблице разработчик за пару минут сверит ожидания с реальной бизнес-логикой. Если в методе есть лишний вызов, проверка счётчика его поймает, а проверка одного только результата пропустит.
Почему это работает
Слабость. Модель и агент оптимизируют видимый сигнал: «компилируется, тесты зелёные». Но зелёный тест не доказывает, что заглушка вернула осмысленное значение, а проверка вызовов зафиксировала правильные аргументы и счётчики. Агент, у которого есть запуск тестов, быстро подгоняет код под прохождение. Для самого теста это не гарантия верности.
Сильная сторона. Модель хорошо читает исходный код и понимает, какие зависимости стоит подменить. Мок-объекты восстанавливаются заметно лучше всего остального. Хуже всего даются выходы заглушек и аргументы с ограничениями в проверках.
Как это использовать. Раздели генерацию на три явных артефакта и проси обоснование для каждого. Так «молчаливые» части (значения заглушек, счётчики вызовов) не прячутся за зелёным результатом. Рычаги: - Сценарии — чем точнее перечислишь, тем меньше модель домысливает. - Требование таблицы «ожидание → причина» — убери, если нужен только код. Оставь, если хочешь ревьюить логику. - «Не подгоняй под реализацию» — это моя защита от подгонки, статья её не проверяла.
Шаблон промпта
Этот шаблон собран по структуре задачи из статьи: тест с вырезанными частями плюс тестируемый метод, на выходе три артефакта. Готовых промптов авторов в доступной части текста нет.
Ты пишешь юнит-тесты на {фреймворк} + {библиотека_моков}.
<Метод>
{код_тестируемого_метода}
Метод>
<Описание_поведения>
{что_метод_должен_делать_по_бизнес-логике}
Описание_поведения>
<Тест_без_моков>
{каркас_теста_с_пропущенными_местами}
Тест_без_моков>
<Сценарии>
{список_сценариев_через_точку_с_запятой}
Сценарии>
Сгенерируй три артефакта, каждый отдельно подписан:
1. МОК-ОБЪЕКТЫ — что подменяем и почему.
2. ЗАГЛУШКИ — что возвращает или выбрасывает подмена в каждом сценарии.
3. ПРОВЕРКИ ВЫЗОВОВ — аргументы, число вызовов, порядок для каждого сценария.
Затем выведи таблицу: сценарий | выход заглушки | ожидаемые вызовы | обоснование.
Ожидания выводи из <Описание_поведения>, а не из того, что делает код.
Подставляй язык и фреймворк, код метода, словесное описание поведения и список сценариев.
🚀 Быстрый старт — вставь в чат:
Вот шаблон генерации мок-тестов. Адаптируй под мою задачу: {твоя задача}.
Задавай вопросы, чтобы заполнить поля.
[вставить шаблон выше]
LLM спросит про язык и фреймворк, зависимости метода и сценарии поведения. Эти данные нужны, чтобы заглушки и проверки вызовов опирались на твою бизнес-логику, а не на догадки модели.
Ограничения
⚠️ Это не метод, а находка: авторы не предлагают и не проверяют промпт, который устраняет разрыв между «проходит» и «верно». Шаблон выше — моя сборка по структуре задачи.
⚠️ Узкая область: речь о юнит-тестах с моками на Java и Python. На другие виды тестов и другие задачи вывод переносится только как общая осторожность.
⚠️ Агенты сравнивались на одной базовой модели: разницу между агентами и чатом нельзя целиком списать на «агентность» и нельзя обобщить на любую модель.
⚠️ Эталон — тесты разработчиков: «верность» меряли близостью к конкретному эталону. Другое разумное поведение тоже могло считаться отклонением.
⚠️ Доступный текст обрывается на описании метрик: конкретные цифры результатов и список десяти типов ошибок в нём отсутствуют. Выводы ниже опираются на заявленные в статье главные находки.
Как исследовали
Исследователи собрали MockBench: 165 задач из реальных проектов. 59 Java-тестов взяты из прежнего исследования StubCoder (Hadoop, Dubbo, Neo4j и др.). Авторы их доработали: проверок результата стало 201 вместо 117, проверок вызовов — 126 вместо 61. Ещё 106 Python-задач из 12 проектов (Flask, spaCy, ansible и др.) разработчики оформили вручную с покрытием кода не ниже 90%.
Идея эксперимента простая. Из рабочего теста вырезают мок-объекты, заглушки и проверки вызовов, а модель должна восстановить их по тестируемому методу. Проверяли шесть LLM (GPT-5.5, Claude Sonnet 4.6, Qwen3.7-Max, DeepSeek-v4-pro, две Qwen3-Coder) и три агента (mini-SWE-agent, OpenCode, Claude Code) на общей модели DeepSeek. Сравнивали по трём осям: проходит ли тест, совпадает ли смысл с эталоном (разбор структуры кода) и ловит ли тест внесённые баги во взаимодействии с зависимостью (мутационный анализ). Эти оси нужны, потому что одно прохождение скрывает отклонения.
Результаты: агенты заметно улучшили исполнимость на Java, но верность и способность ловить баги не выросли. Мок-объекты восстанавливались проще заглушек и проверок вызовов. И LLM, и агенты полностью обошли прежний инструмент StubCoder. Большее время агента подняло исполнимость лишь в небольших пределах, а верность не улучшило. Затем авторы вручную разобрали 660 ошибок и свели их в десять типов. Удивительно, что дополнительная «работа» агента (запуск, правки, проверки) не лечит неверность, а только снимает ошибки компиляции и падения.
Адаптации и экстраполяции
Это мои идеи, статья их не проверяла.
🔧 Техника: добавить ревью-проход → отдельно проверять «молчаливые» части
Теперь выступи ревьюером своих тестов. Для каждого сценария ответь:
(а) что вернёт заглушка и откуда это значение следует в описании поведения;
(б) какой баг в методе поймает проверка вызовов, а какой пропустит проверка результата;
(в) предложи один «ломающий» вариант метода (лишний вызов, не тот аргумент) и скажи, упадёт ли тест.
Это имитирует идею мутационного анализа из статьи. Модель сама придумывает «сломанный» метод и проверяет, заметит ли его тест.
🔧 Экстраполяция: правило в файле инструкций агента (CLAUDE.md / AGENTS.md)
Правила для тестов с моками:
- «Тесты прошли» — не критерий готовности.
- Для каждой подмены явно указывай: что возвращает и почему.
- В каждом сценарии проверяй число вызовов и точные аргументы, а не только результат метода.
- Ожидания выводи из описания поведения, а не из текущей реализации.
- В отчёте перечисли, какие проверки вызовов добавлены и какой дефект они ловят.
Ресурсы
- Работа: «Executable but Unfaithful: Mock Object, Stub, and Assertion Generation by LLMs and Coding Agents»
- Авторы: Hao Ning, Jiahui Dong, Zhen Yang и др. — Shandong University, Henan University, City University of Hong Kong; Jie M. Zhang — Mistral AI и King's College London
- Бенчмарк: MockBench (165 задач, Java и Python)
- Упомянутые инструменты и работы: StubCoder (эволюционный базовый метод), Mockito, pytest-mock, mini-SWE-agent, OpenCode, Claude Code
