Почти все 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+ из этих строк одним махом. Все последующие коммиты по спекам — «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: решаю, какие бизнес-правила заслуживают защиты.