TopicReview の 1,562 行のテストはほぼすべて Claude が書いた。私は review だけ——本番投入から 2 週間以上、新規テスト追加のみで既存テストの書き直しはゼロ。この記事では、なぜテストが Claude に任せるべき最上位の仕事か、review で見るもの見ないもの(実際の spec に潜む見落とし境界を含む)、そしてこの分業のデフォルト構成について。
前回の記事の結びで TopicReview の「119 spec 全部グリーン」と書いた。読者が本当に問うべき次の質問:これらのテストは誰が書いたのか?
答え:Claude がほぼすべての 1,562 行のテストコードを書いた。私は review のみ。本番投入から 2 週間以上が経ち、この 1,562 行のメンテナンスは新規テストの追加のみ、既存テストの書き直しはゼロ。
この記事では、なぜテストは 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 行
4 種類のテストをカバー:service(業務ロジック)、request(controller + integration)、policy(Pundit 権限)、job(スケジュール実行)。
初期コミット d162f1e は Co-Authored-By: Claude Sonnet 4.6 付きで、うち 1,100 行以上を一度に書いた。その後の 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
穴埋めであって、やり直しではない。この細部が後で効いてくる。
硬い理由が 4 つ:
1. 入力と出力が明確。テストの本質は「与えられた状態 → 期待される挙動」。これは Claude が最も得意とする翻訳——仕様をアサーションに変換するだけ。業務ロジックは時に熟慮が必要だが、テストはそうでもない。
2. 機械的 × 大量。describe .open! の一つの下に「陪審員あり / 陪審員なし / topic なし / すでに active な review がある」の 4 context、各 context に 2〜5 の it block。人間は 3 つめの context で省略し始める。Claude は 88 個目の it を 1 個目と同じ丁寧さで書く。
3. フィードバックループが極端に短い。テストを書いて rspec を走らせると、通ったか通らないかがすぐ分かる。業務コードは実運用で数日使って初めて問題が浮かぶ。ループが短いということは、Claude のミスが rspec にその場で拾われる——見張る必要がない。
4. 自然に並列化できる。it block は互いに独立し、隠れた依存がない。Claude が数十の独立テストを一度に書くのはまさに得意領域。
ここが分業全体の肝。
見ない:
見る:
最後の一つが review の真の価値。Claude は「思いつくテスト」は書くが、思いつかないテストは自発的には補完しない。human review のポジションはまさにそこ——業務ルールから逆算してカバレッジの欠落を探す。
spec/services/topic_review_service_spec.rb の冒頭にある describe ".open!" を開く:
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
包括的に見える。だが model の eligible_jurors の実ルールは 3 種類を除外する:
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 は pending_vote_by スコープの数ケースしか扱っていない、eligible_jurors 自体は未テスト。service_spec.rb にはコメントが一行あるだけ:# Jurors must NOT be the post author——setup のコメント、断言ではない。
これが review の見つけるべき欠落:3 種類の除外ルール(原作者 / 通報者 / 初審で投票済み)、どれもテストで保護されていない。後日誰かが eligible_jurors をリファクタリングし、うっかり post.user_id を除外リストから落としても、既存のテストはすべて通る——本番では、原作者が自分の陪審に座ることになる。
Claude は間違っていない。要求された通りのテストを書いた。ただ「この 3 つのルールそれぞれにテスト保護が要りますか?」と自発的に尋ねなかった。この問い——ルールからカバレッジへの逆算——こそ review の仕事。
(正直に言うと、最初の review でも私はこれを見落とした。この記事を書くための 2 回目の audit で気づいた。review も一発で完全にはならない——でもしない場合より 10 倍いい。)
「Claude が書き + human が review」が完璧な分業なら、初期コミット以降に新しいテストコミットは出ないはず。実際はもっと興味深い——穴埋めはあるが書き直しはない:
00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning
最初のは bug 修正後の regression test——e8cb2db Default to keep verdict when review expires with zero votes が fix、00393fc がペアになる追加テスト。2 つ目は abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes を追う形。
この 2 つのコミットが同時に証明するのは:
「十分に良い + 継続的に補修できる」——「完璧」よりはるかに現実的な基準。完璧な review を追うと、テストを Claude に渡せなくなる。「十分に良い」を受け入れることで分業が動き始める。
すべてのテストが完全な委譲に向くわけではない:
こうしたケースでは人間が主導し、Claude が補助する。
この分業を実行可能な default に落とし込む:
プログラマがテストを読むときの心理リソースはコードを読むときより少ない。テストは反復的で、機械的で、消耗するが必須。そのすべてが Claude の強みと重なる——飽きない、疲れない、50 個目の it で手を抜かない。
あなたの仕事は「テストを書くこと」ではなく「すべての業務ルールがテストに守られていることを保証すること」。一方は実装、もう一方は判断——判断は自分に残し、実装は Claude に渡す。
119 spec / 1,562 行が一度のリリースで上がり、2 週間以上ノーリワークで回っている——私が上手く書いたからではなく、私はまったく書いていないから。私が Claude にできない一点だけをやった:どの業務ルールが保護に値するかを判断した。