Tests as the Trust Boundary
Why tests matter more than the implementation in an AI PR: tautological tests, the red-green proof, mutation testing, and what exactly the trust boundary locks in.
Lesson content is currently available in Russian.
What you will learn
- →Ревьюить тесты в AI-PR внимательнее реализации и объяснять, почему граница доверия проходит по ним.
- →Распознавать анти-паттерны сгенерированных тестов: тавтологии, тестирование моков, снапшот всего подряд.
- →Требовать красно-зелёное доказательство и проверять силу тестов мутационным тестированием.
Граница доверия проходит по тестам
Ключевое смещение при работе с AI-кодом: вы физически не можете прочитать всё, что генерируется. Значит, нужно честно ответить на вопрос — чему именно вы доверяете, когда мержите непрочитанный код? Единственный масштабируемый ответ: поведению, которое зафиксировано тестами. Всё, что тесты проверяют, — защищено; всё, что не проверяют, — существует на честном слове модели. Тесты и есть граница доверия: линия, до которой можно не читать реализацию.
Из этого следует инверсия привычного ревью. В человеческом PR тесты часто просматривают по остаточному принципу — «главное, что логика верна». В AI-PR всё наоборот: реализацию можно проверять выборочно (проход 3 из второго урока), а вот тесты читаются полностью и придирчиво, потому что именно они — то, что останется гарантией после мержа. Час ревью, потраченный на тесты, защищает весь будущий код в этой области; час, потраченный на построчное чтение реализации, защищает только этот дифф.
Но здесь ловушка: если тесты писал тот же агент, что и код, они могут фиксировать не требования, а фактическое поведение реализации — включая её баги. Агент, неправильно понявший задачу, напишет тесты под своё неправильное понимание, и они будут зелёными. Поэтому вопрос ревьюера к каждому сгенерированному тесту звучит так: этот тест выведен из требований задачи или срисован с реализации? Ответ различим: тест из требований формулирует сценарий языком домена и содержит ожидания, которые нельзя угадать из кода — граничные значения, запрещённые переходы, коды ошибок из спецификации.
Анти-паттерны сгенерированных тестов
Модель охотно генерирует тесты, которые выглядят как тесты, но ничего не проверяют. Четыре анти-паттерна, которые нужно узнавать мгновенно.
Тавтология — тест сравнивает выход функции с ним же или повторяет логику реализации в ожидании:
// Всегда зелёный: ожидание вычислено той же функцией
it("calculates discount", () => {
expect(calcDiscount(order)).toBe(calcDiscount(order));
});
Тестирование мока — мокается сама проверяемая система, и тест проверяет настройку мока:
jest.mock("./discount", () => ({ calcDiscount: () => 10 }));
it("returns 10", () => {
expect(calcDiscount(order)).toBe(10); // проверили мок, не код
});
Снапшот всего подряд — expect(result).toMatchSnapshot() на большие структуры: тест фиксирует текущее поведение, каким бы оно ни было, включая баги, и молча переутверждается при первом --update.
Только счастливый путь — пять тестов на корректные входы, ноль на ошибки, границы и злонамеренный ввод. Особенно характерно для агентов: happy path легко вывести из кода, а негативные сценарии требуют понимания требований.
Общий признак всех четырёх: тест не может упасть от реального бага. Мысленный эксперимент ревьюера — «какое изменение реализации этот тест поймает?» Если ответ «никакое разумное», тест не расширяет границу доверия, а имитирует её.
Красно-зелёное доказательство и мутационное тестирование
Как проверить, что тест что-то охраняет, не вникая в его логику до конца? Два механических приёма.
Красно-зелёное доказательство: тест обязан падать без своей реализации. Для баг-фикса это железное правило — агент сначала показывает тест, воспроизводящий баг на старом коде (красный), потом фикс, который делает его зелёным. Требуйте оба прогона в описании PR: «вот тест падает на main, вот он проходит в ветке». Для новой функциональности аналог — прогон тестов с откаченной реализацией: git stash реализационной части диффа, тесты должны покраснеть. Тест, зелёный без реализации, — тавтология или тест мока, и он найден за минуту без чтения его кода.
Мутационное тестирование масштабирует эту проверку: инструмент (Stryker для JS/TS, mutmut для Python, PIT для JVM) вносит в код сотни мелких искажений — заменяет > на >=, удаляет ветки, инвертирует условия — и прогоняет тесты на каждом мутанте. Выживший мутант означает: такое искажение кода тесты не замечают. Mutation score честнее покрытия: покрытие говорит «эти строки выполнялись», мутационный балл — «искажение этих строк было бы поймано». Сгенерированные тесты регулярно дают 90% покрытия при посредственном mutation score — идеальная иллюстрация разницы между «тест выполняет код» и «тест охраняет поведение».
В конвейер из прошлого урока это встраивается так: mutation testing на изменённые файлы — периодическая или пороговая проверка (на каждый PR дорого), а красно-зелёное доказательство — обязательное поле в описании каждого AI-PR с багфиксом.
Что требовать от агента: тесты из спецификации
Ревью тестов станет проще, если правильно поставить задачу генерации. Правила для промптов и командных конвенций:
- Тесты до реализации. Просите агента сначала вывести из задачи список сценариев и написать тесты, показать их красными — и только потом реализовывать. Это разворачивает направление зависимости: реализация подгоняется под тесты, а не тесты под реализацию.
- Имена тестов — предложения спецификации. «rejects expired coupon with EXPIRED error code» читается как строка требований. Прочитайте подряд все имена тестов диффа: получилась ли из них внятная спецификация фичи? Если нет — тесты срисованы с кода.
- Обязательные негативные сценарии. В definition of done: на каждый публичный метод — минимум ошибки валидации, граничные значения и поведение при отказе зависимостей. Агенту это нужно сказать явно — сам он остановится на счастливом пути.
- Property-based тесты для алгоритмической логики. Вместо трёх примеров — инвариант на тысячах сгенерированных входов (fast-check, Hypothesis): «сортировка идемпотентна», «парсер не падает ни на какой строке». Инварианты сложно срисовать с реализации — они приходят из требований.
- Контрактные тесты на границах — там, где первый урок предупреждал о тихих изменениях контрактов: снапшоты публичных сигнатур, JSON-схемы ответов API, версии событий. Изменение контракта роняет контрактный тест, и «тихое» становится громким.
Чек-лист ревью тестов в AI-PR: имена читаются как спецификация; есть негативные сценарии; ни один тест не зелёный без реализации; моки не подменяют проверяемую систему; изменения существующих ожиданий обоснованы в описании PR.
Practice
Проверка силы сгенерированных тестов
Возьмите модуль с AI-сгенерированными тестами (или сгенерируйте: функция расчёта скидок с 4–5 правилами плюс тесты). Прогоните мутационное тестирование (Stryker или mutmut), разберите выживших мутантов, классифицируйте тесты по анти-паттернам урока и дополните набор тестами из требований до mutation score 80%+.
Deliverable
Отчёт: mutation score до и после, список выживших мутантов с объяснением «почему тесты его пропустили», найденные анти-паттерны и добавленные тесты с именами-предложениями спецификации.
Checkpoint
Объясните, почему в AI-PR тесты ревьюятся внимательнее реализации, и покажите на примере, как отличить тест из требований от теста, срисованного с реализации.
- ✓ Сформулировано понятие границы доверия: непрочитанному коду доверяют ровно в объёме, зафиксированном тестами.
- ✓ Назван главный риск: агент фиксирует тестами фактическое поведение своей реализации вместе с багами, и тесты зелёные.
- ✓ Приведены механические проверки: красно-зелёное доказательство, тест падает без реализации, мутационное тестирование против метрики покрытия.
Check yourself
Sources