1. «Напиши тесты» — ленивая постановка, ленивый результат
Когда вы впервые начинаете активно использовать Claude для тестов, рука очень быстро тянется именно к команде «напиши тесты под этот PR» — почти автоматически. Удобно — и почти всегда ведёт не туда: для модели это слишком широкая задача. А ленивые постановки обычно дают ленивые результаты.
Если вы просто просите «написать тесты», Claude выберет то, что проще сгенерировать: ближайшую функцию, пару проверок на happy path, красивые имена. В diff смотрится прилично. Но доказали ли эти тесты хоть что-то важное — или вы получили зелёную галочку и моральный комфорт, будто купили спортивную форму и почти сходили в зал?
Вот почему в профессиональной разработке сначала формулируют test strategy, потом пишут test code. Стратегия отвечает не на «как быстро создать тестовый файл», а на «какое поведение опаснее всего оставить без проверки?».
В Commerce OS это особенно показательно на возвратах денег: PR меняет логику POST /orders/{id}/refund. Два unit-теста на калькулятор суммы — не успокоительно: сценарий всё ещё ломается на уровне API, при повторной отправке, из-за неверного окна возврата, на записи в БД. Стратегия говорит: сначала закрыть самые дорогие риски.
Удобно держать в голове такую схему:
flowchart TD
A["TASK_SPEC.md
критерии приёмки"] --> B["Где риск?"]
B --> C["test strategy
отдельный файл или секция"]
C --> D["Только потом
test code"]
D --> E["Diff, checks, PR"]
Хорошее «достаточно» в тестировании звучит не как «мы написали много тестов» и не как «coverage выросла с 62% до 74%», а так: мы собрали достаточно доказательств, что критичное поведение не сломано, а непокрытые риски честно названы. Вот это уже похоже на инженерное мышление, а не на охоту за зелёными квадратиками.
2. L2 в verification-модели и Layer 2 review
На этом месте у многих начинает слегка рябить в глазах от похожих обозначений. Это нормально: когда рядом появляются и verification, и review, мозг иногда ощущает себя как IDE без подсветки синтаксиса. Поэтому лучше сразу спокойно развести термины по разным полкам.
Сегодняшняя лекция — L2 verification, детальный уровень test design внутри уже знакомого плана проверки. L1 отвечает на «чем и как в целом проверим результат». L2 не надстраивает бюрократию, а уточняет: какие тесты дадут лучший сигнал за разумную стоимость. Это не review-гейты, пропускающие PR дальше или нет, — там другая модель, слои называются Layer 1, Layer 2, Layer 3.
Вот короткая таблица, чтобы не путаться:
| Обозначение | Что означает | На какой вопрос отвечает |
|---|---|---|
| L1 | план проверки для задачи | чем и как в целом проверим результат |
| L2 | test design | какие тесты нужны и почему именно они |
| Layer 2 | review в CI/gate | кто и на каком этапе пропускает изменение дальше |
То есть L2 здесь не живёт отдельно от L1: это тот момент, когда общий план проверки превращается в конкретное решение о тестах.
Связь между ними очень простая. Допустим, в TASK_SPEC.md уже есть L1-план крупными мазками:
## План проверки
- добавить regression test на duplicate refund
- прогнать проверку refund endpoint
- сделать быстрый smoke critical path
А вот L2 уже конкретизирует и превращает его в решение дизайна: какой именно regression check, для какого сценария, где хватает unit, где идём выше, что сознательно не трогаем:
## Стратегия тестирования
- проверить duplicate refund как приоритетный regression-риск
- покрыть API-контракт возврата на happy path и double-submit
- зафиксировать smoke для цепочки order -> refund -> ledger entry
- не трогать cross-currency refunds в этом PR
Разница кажется тонкой только на первый взгляд, но на деле она очень важна. L1 говорит: «проверки нужны». L2 говорит: «вот эти дадут лучший сигнал за разумную стоимость». Не код, а инженерное решение.
3. Risk-based testing: что тестировать первым
Risk-based testing на практике гораздо проще и полезнее, чем звучит. Не нужно представлять себе талмуд на сто страниц и менеджера с таблицей в Excel размером с подземный паркинг. Вы оцениваете две оси.
Первая — business risk. Если баг уходит в прод, что реально страдает? Деньги, доступ пользователей, внешний контракт API, доверие, критичный пользовательский путь. В Commerce OS возвраты, платежи, создание заказов, права доступа и публичные API стоят высоко.
Вторая — change risk: насколько рискован сам текущий PR. Большой diff, незнакомый модуль, слабое покрытие, ветвящаяся логика, затронутая интеграция, свежий рефакторинг рядом, много изменённых файлов.
Полезно думать так:
| Что оцениваем | Вопрос | Пример в Commerce OS |
|---|---|---|
| Business risk | Насколько больно, если баг уйдёт в прод? | лишний refund = потеря денег и некорректная отчётность |
| Change risk | Насколько вероятно, что именно этот PR что-то сломает? | затронуты controller, service и запись в БД сразу |
Когда обе оси высокие, тестовый фокус должен быть максимально собранным. Не «покрыть всё подряд», а закрыть сценарии, где цена ошибки максимальна: повторная отправка, потеря идемпотентности, рассинхрон между API-ответом и записью в БД. Для текстовой валидации фильтра каталога хватит стратегии полегче.
Именно здесь начинается зрелое тестирование: вы перестаёте относиться ко всем частям системы как к одинаково важным. Не цинизм, а инженерная честность. Распределяете внимание равномерно — значит распределяете плохо.
В таком подходе чаще всего всплывают одни и те же зоны внимания: критичные бизнес-сценарии, недавние изменения, аутентификация и права доступа, валидация входных данных, операции с деньгами и данными, внешние интеграции, публичные API-контракты, обработка ошибок, баги, которые уже случались, и области с известным слабым покрытием. Не «тестировать всё» — а сигналы, куда смотреть первым.
Самая взрослая часть этого подхода — блок risks not covered. Он выглядит скромно, но на деле это очень сильный инструмент. «Cross-currency refunds не покрываем этим PR» — не признание слабости: вы показываете, что понимаете границы изменения и не продаёте иллюзию защищённости.
4. Coverage: датчик, но не цель
У покрытия репутация слегка трагическая. Цифра понятная, график красивый — за измеримость его любят и за неё же превращают в фетиш. А фетиши в инженерии — штука обычно дорогая.
Самая точная аналогия здесь такая: coverage похожа на шагомер. 10 000 почти наверняка лучше, чем 300. Но сам по себе шагомер не доказывает, что вы пришли туда, куда хотели: можно намотать круги по квартире и никуда не добраться. В тестах та же история — высокий процент легко получить дешёвыми тестами на мелкие функции, замокав всё подряд и проверяя детали реализации вместо поведения. Отчёт будет красивый. Уверенность — фальшивая.
Сравните две ситуации:
| Ситуация | Coverage | Насколько это успокаивает |
|---|---|---|
| Много unit-тестов на форматтеры, мапперы и вспомогательные функции, но refund flow почти не затронут | 90% | Слабо: критичный риск всё ещё жив |
| Меньше общее покрытие, но защищены duplicate refund, API-контракт возврата и smoke критичного пути | 55% | Сильно: дорогой риск закрыт доказательствами |
Это не аргумент против покрытия как такового. Как датчик coverage полезна: подсказывает слепые зоны, где база провисает, где после рефакторинга внезапно исчезли проверки. Но как цель работает плохо. Скажете «добить до 80%» — команда начнёт добивать процент. Скажете «доказать, что refund-сценарий безопасен» — разговоры станут другими.
Поэтому в хорошей стратегии цифра может упоминаться, но не быть смысловым центром документа. Центр — поведение, риски и причины выбора. Остальное — телеметрия.
5. Хорошая test strategy короткая и рабочая
На этом этапе хочется получить артефакт, который можно открыть и реально работать, а не медитировать на правильные слова. И это хорошая новость: длинный документ здесь обычно хуже короткого — стратегия-роман перестаёт читаться ровно тогда, когда особенно нужна.
Для средних и рискованных изменений её удобно оформлять отдельным TEST_STRATEGY.md. В небольшом bugfix та же структура спокойно живёт секцией в TASK_SPEC.md, в описании PR или в заметке для review. Важна не магия имени, а решение о приоритетах.
Ниже пример нормального, рабочего артефакта для refund flow в Commerce OS:
# Стратегия тестирования — refund flow (T-01)
## Unit
- refund amount calculation: full, partial, boundary values
## Интеграция / API
- POST /orders/{id}/refund: happy path, double-submit, expired window
- refund record created exactly once on retry
## Smoke
- critical path: place order -> approve refund -> ledger entry
## Ручные проверки
- admin UI shows updated refund state immediately
## Не покрытые риски
- cross-currency refunds
- partial refund cancellation flow
Чем хорош такой файл? Во-первых, он короткий. Во-вторых, не спорит про фреймворки, не расписывает соглашения об именовании, не притворяется двадцатичасовым курсом — фиксирует решение о приоритетах.
Секции здесь полезно воспринимать не как экзаменационные вопросы, а как полки в шкафу. Unit — изолированная логика. Integration / API — где нужен более реалистичный сигнал. Smoke — короткая проверка жизнеспособности critical path. Manual checks — то, что пока невыгодно автоматизировать. Risks not covered — честная фиксация границ.
Очень важный нюанс: стратегия пишется до test code, не как посмертное объяснение того, что нагенерировал Claude. Сначала тестовые файлы, а стратегию задним числом — и документ выйдет декоративным: не управляет работой, а описывает её.
И ещё один тихий, но важный критерий качества: в стратегии видно, чего вы не делаете. У начинающих часто есть ощущение, что документ обязан изображать полноту, — а сильный инженерный документ умеет ограничивать.
6. Claude помогает со стратегией, но не решает за вас
Теперь самое интересное: как встроить Claude в этот процесс так, чтобы он действительно ускорял работу, а не превращал тестирование в красивую случайность? Главный принцип здесь очень простой: Claude собирает кандидатов и структурирует решение, но не решает за вас, что считать достаточным доказательством.
Хороший первый запрос выглядит так:
Read TASK_SPEC.md and the affected files under src/refunds/.
Propose a risk-prioritized test strategy:
- what to test first,
- at what level,
- what not to test in this PR and why.
Do not write test code yet.
Обратите внимание на последнюю строку — она здесь делает почти всю магию. Без неё модель очень быстро соскальзывает из режима проектирования в режим генерации, а нам сейчас нужен именно дизайн.
Если вы используете Workflow Kit, это поведение полезно закрепить отдельным skill — его SKILL.md:
---
name: test-strategy
description: Build a risk-prioritized test strategy before writing any test code.
when_to_use: before tests for a feature, bugfix or refactor
allowed_tools: read, grep, run_tests_read_only
---
Ограничения:
- Do not write test code.
- Coverage % is not the goal.
- Always include "Risks not covered".
Такой skill особенно хорош тем, что выносит правильное поведение из головы разработчика в поддерживаемый артефакт команды. Новая сессия, другой человек, свежий Claude — правило остаётся.
Отдельно стоит помнить про контекст. Тестовая работа — хороший кандидат для отдельной, более чистой сессии или read-only исследования. В длинной implementation-сессии, где уже спорили с логами и пару раз ходили не туда, стратегия расплывается. Нужна ясность: task spec, affected files, тесты, риски — ничего лишнего.
Если сформулировать всё совсем коротко: вы не просите «сделай тесты», вы просите помочь принять решение о тестовом дизайне. И когда на руках внятная test strategy, Claude ещё не написал ни строчки test code — но самое важное инженерное решение уже принято: какой риск вы закрываете сейчас, каким доказательством и что честно оставляете за пределами этого PR.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ