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: повне, часткове, граничні значення
## Інтеграція / API
- POST /orders/{id}/refund: happy path, double-submit, прострочене вікно
- refund record створюється рівно один раз за повторної спроби
## Smoke
- критичний шлях: оформити замовлення -> затвердити refund -> ledger entry
## Ручні перевірки
- admin UI одразу показує оновлений стан refund
## Ризики, що не покриті
- cross-currency refunds
- flow скасування часткового refund
Чим хороший такий файл? По-перше, він короткий. По-друге, не сперечається про фреймворки, не розписує угоди про іменування, не вдає двадцятигодинний курс — фіксує рішення про пріоритети.
Секції тут корисно сприймати не як екзаменаційні запитання, а як полиці в шафі. 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.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ