Майже всі 1562 рядки тестів TopicReview написав Claude. Я лише роблю review — уже понад два тижні у проді, усі подальші коміти додають тести, жоден не переписує. Цей пост про те, чому тести — ідеальна ціль для делегування, на що дивитися і що ігнорувати у review (включно з реально відсутнім edge case у spec) і про дефолтну конфігурацію цього розподілу.
Попередній допис закрили фразою «119 spec зелені» для TopicReview. Справжнє наступне питання: хто ці тести писав?
Відповідь: Claude написав майже всі 1562 рядки тестового коду. Я лише роблю review. Уже понад два тижні в проді, і шаблон супроводу цих 1562 рядків такий — лише додавати нові тести, ніколи не переписувати старі.
Цей допис — про те, чому тести є найкращим кандидатом для делегування Claude, на що дивитися і що ігнорувати в review, і наскільки далеко цей розподіл реально працює.
Тести TopicReview розкидані по 7 файлах:
spec/services/topic_review_service_spec.rb 760 рядків (88 тестів)
spec/requests/topic_reviews_spec.rb 281 рядок (32 тести)
spec/requests/review_appeals_spec.rb 152 рядки (16 тестів)
spec/requests/review_votes_spec.rb 127 рядків
spec/policies/topic_review_policy_spec.rb 109 рядків
spec/jobs/close_topic_review_job_spec.rb 71 рядок (7 тестів)
spec/models/topic_review_spec.rb 62 рядки
───────────────────────────────────────────
1 562 рядки
Охоплено чотири типи тестів: service (бізнес-логіка), request (controller + інтеграція), policy (авторизація Pundit), job (заплановані задачі).
Початковий коміт d162f1e має помітку Co-Authored-By: Claude Sonnet 4.6 і вносить 1100+ із цих рядків за один захід. Усі подальші коміти по spec — «Add test for...»; жодного refactor чи rewrite:
00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning
3b185da Update specs to use PROVISIONAL_PENALTY constant
Латання отворів, не переробка. Ця деталь ще спливе.
Чотири жорсткі причини:
1. Входи й виходи явні. Тест, по суті, — «дано цей стан → очікуй цю поведінку». Це сильний бік Claude: перекласти специфікацію в assertion. Бізнес-код іноді вимагає компромісів; тести — майже ніколи.
2. Механічно × великий обсяг. Один describe .open! має покрити «є придатні присяжні / немає / немає topic / уже є активний review» — чотири context-и, по 2–5 it у кожному. Людина на третьому context-і починає зрізати кути. Claude пише 88-й it із тим самим тщанням, що й перший.
3. Дуже коротка петля зворотного зв'язку. Пишеш тест, запускаєш rspec, за секунди бачиш — пройшов чи ні. Бізнес-код потребує днів реального використання, щоб проблеми виплили. Коротка петля = будь-яку помилку Claude rspec ловить на місці — тобі не треба стерегти.
4. Природно паралельно. Блоки it незалежні, прихованих зв'язків нема, масштабуються тривіально. Згенерувати десятки ізольованих тестів за раз — саме те, у чому сильний Claude.
Це вісь усього розподілу.
Ігнорувати:
Дивитися:
Останній пункт — справжня цінність review. Claude покриває тести, «які йому спали на думку», але ті, що не спали, самі не з'являться. Саме сюди вписується людське review — іти назад від бізнес-правил до відсутнього покриття.
Відкриваємо початок describe ".open!" у spec/services/topic_review_service_spec.rb:
describe ".open!" do
context "when there are eligible jurors" do
# статус review коректний / post under_review / assignments створені / author повідомлений / jurors повідомлені / подвійного open нема
end
context "when there are no eligible jurors" do
# review створюється, але assignments — ні
end
context "when post has no topic" do
# повертає nil
end
end
Виглядає вичерпно. Але реальне правило eligible_jurors у моделі виключає три групи:
def eligible_jurors
excluded_ids = [ post.user_id ] + post.reports.pluck(:user_id) + review_votes.where(stage: :initial).pluck(:user_id)
User.jurors_and_judges.where.not(id: excluded_ids.uniq)
end
Тепер подивіться на тести — який тест стверджує, що «автор поста ніколи не обирається присяжним»?
Проходиш service_spec.rb і model_spec.rb: нема такого. model_spec.rb тестує лише кілька випадків scope pending_vote_by; eligible_jurors напряму не покриває. У service_spec.rb є лише коментар: # Jurors must NOT be the post author — це в setup, не assertion.
Ось що ловить review: три правила виключення (автор / скаржник / уже-проголосував-в-initial), жодне не захищене тестом. Якщо потім хтось рефакторить eligible_jurors і ненароком випустить post.user_id зі списку виключень, усі наявні тести пройдуть — а прод тихо пустить авторів до їхньої власної колегії присяжних.
Claude не помилився — він протестував те, що його просили. Він просто сам не запитав: «кожне з цих трьох правил потребує тестового покриття?» Це питання — від правил до покриття назад — і є роботою review.
(Чесно: я сам пропустив це у першому review. Помітив лише під час другого аудиту вже пишучи цей допис. Тобто й review — не «раз і готово» — але все одно у 10 разів краще, ніж без review.)
Якби «Claude пише + людина ревʼює» було ідеальним розподілом, після початкового коміту не було б нових тест-комітів. Реальність цікавіша — латання отворів без переписування:
00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning
Перший — регресивний тест після баґа — e8cb2db Default to keep verdict when review expires with zero votes це фікс, 00393fc — парний тест. Той самий патерн у другого, слідом за abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.
Ці два коміти доводять дві речі одразу:
«Досить добре + можна далі латати» — набагато реалістичніша планка за «ідеально». Гонитва за ідеальним review — саме те, що заважає віддати тести Claude. Прийняти «досить добре» — те, що запускає цей розподіл.
Не кожен тест підходить для повного handoff-у:
У цих випадках веде людина, Claude асистує.
Перетворимо цей розподіл на виконуваний default:
У програміста менше ментального ресурсу на читання тестів, ніж на читання коду. Тести повторювані, механічні, виснажливі й потрібні. Усе це — сильна зона Claude — йому не нудно, не втомлено, він не зрізає кутів на 50-му it.
Твоя робота не «писати тести» — а «гарантувати, що кожне бізнес-правило покрите тестом». Одне — реалізація, інше — судження. Судження лишається в тебе; реалізація — до Claude.
119 spec / 1562 рядки викладено одним комітом і живуть понад два тижні без переробки — не тому, що я краще пишу тести, а тому, що я не написав жодного. Я роблю лише те, чого не робить Claude: вирішую, які бізнес-правила варто захищати.