Проблема: тесты зелёные, задача не та
AI-агент открыл pull request. Изменения выглядят аккуратно. Тесты зелёные. На ревью выясняется, что агент выбрал другую трактовку правила: клиент получает ответ, второй заказ при этом всё равно появляется. Код и тест согласованы друг с другом. Команда хотела проверить другое поведение.
В этом месте легко обвинить модель, промпт или ревьюера. Проблема возникает раньше: агент получил задачу без заранее согласованного ответа на вопрос «какой наблюдаемый результат должен доказать тест?».
В All Smoke, No Alarm исследователи изучили 86 156 изменений тестовых файлов в 33 596 pull requests, созданных кодинг-агентами в 2 807 GitHub-репозиториях. В 80,2% проанализированных изменений авторы не нашли сильного явного сигнала проверки ожидаемого результата. Это наблюдение для конкретной выборки, пяти агентов и классификации по форме изменений, а не по смыслу теста; оно не измеряет долю «плохих тестов всех AI-агентов». Риск при этом понятен: агент способен написать тест под собственную трактовку задачи и успешно его пройти.
TL;DR
Перед изменением кода человек утверждает короткий сценарий приёмки: что уже произошло, какое действие выполняется, что увидит пользователь или система, какой неверный результат проверка обязана поймать.
Агент реализует задачу и связывает сценарий с конкретным тестом и запуском CI. На ревью команда проверяет путь требование → сценарий → тест → результат CI → решение владельца.
Этот артефакт занимает один файл рядом с задачей. Он не заменяет контракт делегирования: контракт ограничивает полномочия агента, карта приёмки фиксирует смысл результата.
Решение: карта приёмки для одной задачи
Назовём артефакт картой приёмки. Для заметного изменения поведения она хранит пять звеньев:
Требование
→ сценарий приёмки
→ тест или ручная проверка
→ результат CI
→ решение владельца
Карта отвечает на один практический вопрос: «Почему именно этот зелёный тест подтверждает нужное изменение?»
Минимальный шаблон:
requirement:
id: R-1
result: Что должно измениться для пользователя или операции
owner: Кто подтверждает смысл изменения
scenario:
id: AT-1
given: Что уже произошло
when: Какое действие выполняется
then: Что наблюдает пользователь или система
rejects:
- Какой правдоподобный неверный результат должен быть пойман
verification:
id: VT-1
covers: AT-1
test: Путь к тесту или описание ручной проверки
base_commit_result: Что произошло на исходной версии
candidate_result: Что произошло на версии с изменением
ci_evidence: Ссылка на запуск CI
reviewer: Кто принимает доказательства
Четыре поля сценария и две связи вне его важнее красивой YAML-разметки.
- Given / when / then удерживают разговор на наблюдаемом поведении.
- Rejects заставляет назвать ошибку, которую тест обязан заметить.
- Владелец требования не оставляет смысл изменения на усмотрение автора кода.
- Связь с CI позволяет открыть фактический запуск проверки, а не читать пересказ в PR.
Разбор решения на одной задаче
Возьмём ограниченную задачу ORD-184: исправить обработку повторного запроса заказа и добавить интеграционный тест, который доказывает нужное поведение.
Платёжный сервис создаёт заказ по запросу клиента. Сеть может оборваться после ответа, и клиент повторит тот же запрос с тем же ключом идемпотентности. Система обязана вернуть исходный заказ. Второй заказ и второе списание появиться не должны.
Агент получает доступ к модулю заказов и тестов, работает в ветке и открывает PR. Он не меняет схему базы данных, публичный API, правила расчёта или настройки в рабочей среде. Если спецификация и код дают разные ответы, агент останавливается и передаёт вопрос владельцу. Эти границы задаёт контракт делегирования.
Шаг 1. До кода владелец утверждает сценарий
Владелец смысла — product owner, domain owner или Tech Lead, в зависимости от устройства команды — подтверждает такую запись:
requirement:
id: R-1
change: ORD-184
result: Повторный запрос с тем же idempotency key не создаёт второй заказ
owner: Технический владелец сервиса заказов
scenario:
id: AT-1
given: Первый валидный запрос уже создал заказ и провёл списание
when: Клиент повторяет тот же запрос с тем же idempotency key
then: API возвращает исходный заказ; второго заказа и второго списания нет
rejects:
- Создаётся второй заказ
- API отвечает успешно, хотя появляется второй платёж
Вопрос на этом шаге один: если этот сценарий пройдёт, можно считать нужный результат достигнутым?
Здесь ещё нет тестового кода и спор о реализации не успел заменить разговор о поведении. Если владельцу нужен другой сценарий — например, отдельная реакция на запрос с тем же ключом, но другим телом, — он появляется до работы агента.
Шаг 2. Агент связывает сценарий с тестом
Агент добавляет или обновляет интеграционный тест и записывает связь:
verification:
id: VT-1
covers: AT-1
test: tests/orders/idempotency.integration.spec.ts
base_commit_result: fails
candidate_result: passes
ci_evidence: https://ci.example/runs/123
Проверка запускается на исходной версии и падает по ожидаемой причине: система ещё не подтверждает зафиксированный сценарий. На версии с изменением тест проходит. Это даёт ревьюеру сильный сигнал: тест связан с конкретным поведением, а не просто оказался зелёным рядом с новым кодом.
У правила есть рабочие исключения. Тест может закрывать существующее поведение после рефакторинга. Среда может быть недетерминированной. В таких случаях в карте остаётся короткое объяснение, почему шаг с падением на исходной версии неприменим.
Шаг 3. Ревьюер проверяет доказательство, а не пересказ агента
В PR ревьюер открывает карту, тест и запуск CI. Проверка занимает несколько вопросов:
- Сценарий описывает результат для клиента или системы, а не вызов внутреннего метода?
- Тест действительно покрывает
AT-1? - Сценарий поймает второй заказ и второе списание, или проверяется только успешный HTTP-ответ?
- Агент не поменял сценарий после того, как выбрал реализацию?
- Запуск CI относится к этому набору изменений?
Если ответ на один из вопросов неясен, PR возвращается на уточнение. Агент может предложить новый сценарий. Решение изменить смысл правила остаётся у владельца.
Шаг 4. Владелец принимает результат
Владелец видит исходное требование, сценарий, ссылку на тест, фактический результат CI и PR. Его решение больше не строится на фразе «агент всё проверил».
В этом примере он принимает один конкретный факт: при повторном валидном запросе с тем же ключом система возвращает исходный заказ и не создаёт второй заказ или второе списание.
Почему одного зелёного CI недостаточно
Зелёный CI отвечает на технический вопрос: прошёл ли код тот набор проверок, который запустили.
Карта приёмки добавляет три ответа, которых в CI обычно нет:
- Кто подтвердил, что именно это поведение нужно.
- Какой неверный результат сценарий обязан отвергнуть.
- Где увидеть связь между утверждённым сценарием, тестом и запуском проверки.
SWE-ABS усилил набор тестов в SWE-Bench Verified дополнительными проверками и правдоподобными неверными вариантами реализации. Усиленный набор отклонил 2 184 из 11 041 патчей, которые проходили исходные тесты. Это бенчмарк, не статистика рабочих систем. Практический вывод остаётся полезным: прохождение исходного набора тестов не закрывает вопрос о качестве изменения.
Где хранить карту
Для одной задачи достаточно файла рядом с контрактом и кодом:
.agent/
contracts/ORD-184.json
acceptance/ORD-184.yml
Команда, которая использует OpenSpec, хранит требования и сценарии в OpenSpec. Рядом с изменением остаётся только краткая связь сценарий → тест → результат CI и отметка согласования. Так требования не размножаются в двух источниках.
SKA Observatory в стратегии тестирования описывает похожую дисциплину для функциональности и возможностей системы: product owner и команда определяют тесты приёмки, отдельная функция проверяет план тестирования, непокрытые риски получают явное объяснение. Команде не нужно копировать эту систему целиком. Достаточно сохранить её полезную часть: значимый сценарий приёмки становится общим решением до реализации.
В ORD-184 владелец сервиса заказов видит ровно то, что нужно для решения: утверждённый сценарий повторного запроса, тест, запуск CI и PR. Этого достаточно, чтобы принять изменение или вернуть агенту конкретный вопрос.