Модуль 1 · Рабочий цикл · урок 6 из 17
Тесты как спецификация: TDD с агентом
С агентом TDD перестаёт быть вопросом дисциплины и становится вопросом контроля. Тесты, написанные до кода, — это спецификация, которую агент не может истолковать по-своему: он её либо прошёл, либо нет. А вы внимательно читаете не сотни строк реализации, а несколько десятков строк тестов.
Почему TDD и агенты хорошо сочетаются
Агенту нужен способ понять, что задача решена. Руководство OpenAI по лучшим практикам повторяет это несколько раз: Codex может сам писать тесты, запускать проверки и подтверждать результат, но только если знает, как выглядит «хорошо». Тест — самая точная форма такого знания, потому что он исполняется.
Саймон Уиллисон в гайде Agentic Engineering Patterns выделяет red/green TDD в отдельный приём работы с агентами. Сначала тест, который падает (red), потом реализация, после которой он проходит (green). Красный тест подтверждает, что тест вообще что-то проверяет. Зелёный — что код делает заявленное.
Для вас TDD с агентом переносит точку контроля. Вместо того чтобы вычитывать всю реализацию, вы вычитываете тесты: их меньше, и в них видно намерение.
Цикл по шагам
Пример: в плагине интернет-магазина на OctoberCMS нужна акция «третий товар бесплатно, если все три из одной категории; бесплатным становится самый дешёвый».
1. Тесты из критериев. Только тесты, без кода:
Правило акции — ниже. Напиши тесты PHPUnit для DiscountCalculator
в tests/Unit/DiscountCalculatorTest.php. Покрой основной сценарий
и граничные случаи: два товара, четыре товара, разные категории,
одинаковые цены, пустая корзина. Реализацию не пиши и пока ничего
не запускай — сначала покажи тесты.Граничные условия — сильная сторона агента. В гайде OpenAI о внутреннем использовании Codex отмечено, что он хорошо находит пустые входы, предельные длины и необычные, но допустимые состояния. Ваша задача — убедиться, что эти случаи отражают бизнес-правило, а не догадку агента.
2. Вы утверждаете тесты. Это главный шаг. Читайте тесты как спецификацию:
public function test_cheapest_of_three_same_category_is_free(): void
{
$cart = $this->makeCart([
['price' => 1000, 'category' => 'shoes'],
['price' => 700, 'category' => 'shoes'],
['price' => 1500, 'category' => 'shoes'],
]);
$this->assertSame(700, (new DiscountCalculator())->discountFor($cart));
}К каждому тесту — вопрос: это требование бизнеса или выдумка агента? Что при четырёх товарах — бесплатен один или два? Если в критериях этого нет, вопрос к аналитику, а не к агенту. Неясность, найденная на этапе тестов, обходится дешевле, чем найденная тестировщиком или клиентом.
3. Красный. Агент запускает тесты и показывает, что они падают — потому что нет реализации, а не из-за ошибки в самом тесте:
vendor/bin/phpunit --filter DiscountCalculatorTest4. Зелёный. Реализация с жёстким ограничением:
Тесты утверждены. Реализуй DiscountCalculator так, чтобы они прошли.
Файлы в tests/ не меняй. Если считаешь, что тест неверен, остановись
и объясни, не исправляя его.5. Рефакторинг. Когда всё зелёное, можно попросить упростить реализацию — тесты страхуют.
Как агент обходит тесты
Даже с запретом агенты иногда находят обходные пути. Что проверять в диффе:
- Изменения в тестах.
git diff --stat main -- tests/должен показывать только утверждённое. - Подгонка под тест. Реализация с условиями на конкретные значения из тестов — классика. Помогает после реализации добавить случай, которого агент не видел.
- Моки всего. Тест, в котором замокано всё, кроме одного
return, проверяет моки. Для калькулятора скидок моки вообще не нужны — это чистая логика. - Тест на реализацию. Проверка «метод
applyRuleвызван дважды» ломается при любом рефакторинге и ничего не говорит о поведении. Просите проверять результат, а не внутренние вызовы. - Пропуски.
markTestSkipped, перенос неудобного теста в медленную группу, фильтр, который запускает не всё.
Подробный чек-лист проверки тестов, написанных агентом, — в курсе для тестировщиков: «Как проверять тесты, написанные агентом».
Закрепите правила в AGENTS.md
## TDD
- Для новой бизнес-логики: сначала тесты, покажи их до реализации.
- Новый тест должен упасть до реализации — покажи вывод.
- Утверждённые тесты не меняй без согласования.
- Проверяй результат, а не внутренние вызовы.
- Один тест: vendor/bin/phpunit --filter ИмяТеста; весь набор: composer test.Где TDD не работает
Не всё удобно начинать с теста. Вёрстку по макету проверяют глазами и скриншотами — об этом урок «Вёрстка по макету». Исследовательский прототип выбросят. Интеграции с внешним API, у которого нет тестового режима, сначала нужен контракт. В таких случаях работает облегчённый вариант: зафиксировать ожидаемое поведение хотя бы одним тестом после реализации, но до MR.
И последнее: зелёные тесты, написанные агентом, — ещё не доказательство, что поведение правильное. Проверка поведения остаётся одним из главных открытых вопросов агентной разработки; ему посвящён урок «Проверка поведения».
Попробуйте сами
10–15 мин на рабочем месте- Возьмите небольшое бизнес-правило из текущей задачи и попросите агента написать только тесты. Найдите хотя бы один тест, не соответствующий правилу, или один пропущенный случай.
- Пройдите цикл «красный → зелёный» с запретом менять
tests/. После реализации проверьтеgit diff --statпо каталогу тестов. - После реализации добавьте тестовый случай, которого агент не видел, и посмотрите, проходит ли он.
Коротко
- Тесты до кода — спецификация, которую агент не может истолковать по-своему.
- Агент пишет тесты, вы их утверждаете — это главная точка контроля.
- Красный тест должен падать из-за отсутствия реализации, а не из-за ошибки в тесте.
- Реализация — без права менять утверждённые тесты; проверяйте дифф
tests/. - Следите за подгонкой под тест, тотальными моками и тестами на внутренние вызовы.