Free

Lass Claude die Tests schreiben, du machst nur das Review: 1.562 Zeilen in der Praxis

Fast alle 1.562 Zeilen der TopicReview-Tests wurden von Claude geschrieben. Ich mache nur das Review — über zwei Wochen im Produktivbetrieb, alle Folge-Commits fügen Tests hinzu, keiner schreibt sie neu. Dieser Beitrag handelt davon, warum Tests das ideale Delegationsziel sind, worauf man beim Review schauen und was man ignorieren sollte (einschließlich eines tatsächlich fehlenden Edge Case im Spec), und der Standardkonfiguration dieser Aufteilung.


Der letzte Beitrag endete mit „119 Specs grün" für TopicReview. Die eigentliche Folgefrage: Wer hat diese Tests geschrieben?

Antwort: Claude hat nahezu alle 1.562 Zeilen Testcode geschrieben. Ich mache nur das Review. Seit über zwei Wochen in Produktion, und das Wartungsmuster dieser 1.562 Zeilen lautet nur neue Tests hinzufügen, alte niemals umschreiben.

Dieser Beitrag handelt davon, warum Tests der beste Delegationskandidat für Claude sind, worauf beim Review zu achten ist und was man ignorieren sollte, und wie weit diese Aufteilung in der Praxis trägt.

Erst die Zahlen

Die TopicReview-Tests verteilen sich auf 7 Dateien:

spec/services/topic_review_service_spec.rb   760 Zeilen (88 Tests)
spec/requests/topic_reviews_spec.rb          281 Zeilen (32 Tests)
spec/requests/review_appeals_spec.rb         152 Zeilen (16 Tests)
spec/requests/review_votes_spec.rb           127 Zeilen
spec/policies/topic_review_policy_spec.rb    109 Zeilen
spec/jobs/close_topic_review_job_spec.rb      71 Zeilen (7 Tests)
spec/models/topic_review_spec.rb              62 Zeilen
───────────────────────────────────────────
                                          1.562 Zeilen

Vier Testarten abgedeckt: service (Geschäftslogik), request (Controller + Integration), policy (Pundit-Autorisierung), job (geplante Tasks).

Der initiale Commit d162f1e trägt Co-Authored-By: Claude Sonnet 4.6 und landet 1.100+ dieser Zeilen auf einen Schlag. Jeder nachfolgende Spec-Commit ist „Add test for..."—kein einziger refactor oder 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

Lücken stopfen, nicht neu machen. Dieses Detail kommt später wieder.

Warum Tests der beste Delegationskandidat sind

Vier harte Gründe:

1. Eingaben und Ausgaben sind explizit. Ein Test ist „gegeben dieser Zustand → erwarte dieses Verhalten". Das ist Claudes stärkste Übersetzungsaufgabe: Spec in Assertion verwandeln. Geschäftslogik verlangt manchmal Abwägungen; Tests fast nie.

2. Mechanisch × große Menge. Ein einziges describe .open! muss „hat geeignete Jurymitglieder / hat keine / kein Topic / bereits aktives Review" abdecken—vier Contexts mit je 2–5 it-Blöcken. Menschen beginnen beim dritten Context, Ecken abzuschneiden. Claude schreibt das 88. it mit derselben Sorgfalt wie das erste.

3. Extrem kurze Rückkopplung. Test schreiben, rspec laufen lassen, in Sekunden wissen, ob er grün ist. Geschäftscode braucht Tage Realbetrieb, bis Probleme auftauchen. Kurze Schleife = jeder Claude-Fehler wird sofort von rspec gefangen—du musst nicht babysitten.

4. Natürlicher Parallelismus. it-Blöcke sind unabhängig, keine versteckten Kopplungen, trivial skalierbar. Dutzende isolierte Tests auf einmal zu erzeugen, ist genau Claudes Stärke.

Was man im Review anschaut und was man ignoriert

Das ist der Angelpunkt der gesamten Aufteilung.

Ignorieren:

  • Ob die RSpec-Syntax stimmt → Claude macht das fast nie falsch
  • Mock-Qualität → solange kein offensichtliches Over-Mocking, in Ordnung
  • Ästhetik der Factories → unwichtig, läuft ist läuft
  • Stilkonsistenz → wenn etwas nicht passt, fixt Claude alles auf eine Anweisung hin

Anschauen:

  • Ob die Edge Cases tatsächlich abgedeckt sind
  • Ob die Testnamen das echte erwartete Verhalten beschreiben
  • Ob Tests fehlen, die eigentlich da sein sollten

Der letzte Punkt ist der eigentliche Wert des Reviews. Claude deckt die Tests ab, „die ihm einfallen", aber die, die ihm nicht einfallen, entstehen nicht von selbst. Genau dort setzt menschliches Review an—rückwärts von den Geschäftsregeln zur fehlenden Abdeckung laufen.

Ein konkretes Beispiel: was das Review tatsächlich findet

Öffne den Anfang des describe ".open!" in spec/services/topic_review_service_spec.rb:

describe ".open!" do
  context "when there are eligible jurors" do
    # Review-Status ok / post under_review / assignments erstellt / author benachrichtigt / jurors benachrichtigt / kein Doppel-Open
  end
  context "when there are no eligible jurors" do
    # Review wird erstellt, aber keine Assignments
  end
  context "when post has no topic" do
    # gibt nil zurück
  end
end

Sieht umfassend aus. Aber die echte Regel von eligible_jurors im Model schließt drei Gruppen aus:

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

Schau dir jetzt die Tests an—welcher Test behauptet, dass „der Autor des Posts nie als Juror ausgewählt wird"?

Such in service_spec.rb und model_spec.rb: nichts. model_spec.rb testet nur ein paar Fälle des pending_vote_by-Scopes; eligible_jurors selbst ist nicht abgedeckt. In service_spec.rb steht nur ein Kommentar: # Jurors must NOT be the post author—das steht im Setup, ist keine Assertion.

Genau das fängt das Review: drei Ausschlussregeln (Autor / Melder / hat-im-Initial-schon-gewählt), keine davon ist durch einen Test abgesichert. Refaktoriert später jemand eligible_jurors und fällt post.user_id aus der Ausschlussliste, laufen alle bestehenden Tests grün—und in Produktion setzen Autoren plötzlich in ihrer eigenen Jury.

Claude lag nicht falsch—er hat getestet, was verlangt war. Er hat nur nicht von selbst gefragt: „brauchen diese drei Regeln jeweils Testabdeckung?" Diese Frage—von Regeln zur Abdeckung rückwärts—ist Sache des Reviews.

(Ehrlich: ich habe das beim ersten Review auch übersehen. Ich habe es erst bei einem zweiten Audit beim Schreiben dieses Posts bemerkt. Also ist Review auch kein One-Shot—aber immer noch 10× besser als kein Review.)

Die Folge-Commits belegen, dass die Aufteilung in der Praxis trägt

Wäre „Claude schreibt + Mensch reviewt" perfekt, gäbe es nach dem Initial-Commit keine neuen Test-Commits. Was tatsächlich passierte, ist interessanter—Lücken füllen ohne Neu-Schreiben:

00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning

Der erste ist ein Regressionstest nach einem Bug—e8cb2db Default to keep verdict when review expires with zero votes war der Fix, 00393fc der zugehörige Test. Dasselbe Muster für den zweiten, als Folge von abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.

Diese zwei Commits belegen zwei Dinge gleichzeitig:

  • Review hat nicht 100 % der Fälle gefangen—deshalb hat Produktion zwei Bugs aufgedeckt
  • Aber die Testarchitektur hat getragen; wir konnten ohne Umbau weitere Tests ergänzen—deshalb heißen die Commits „Add test for..." und nicht „Rewrite ... spec"

„Gut genug + kontinuierlich nachbesserbar" ist eine viel realistischere Messlatte als „perfekt". Dem perfekten Review hinterherzurennen ist genau das, was dich davon abhält, Tests an Claude zu übergeben. „Gut genug" zu akzeptieren bringt die Aufteilung zum Laufen.

Tests, die nicht komplett an Claude gehen sollten

Nicht jeder Test eignet sich für vollständigen Handoff:

  • E2E Happy-Paths—brauchen Produktblick. Claude kann sie schreiben, deckt aber oft nur „technisch durchläufbar" ab und verfehlt „wo Nutzer wirklich hängen bleiben"
  • Sicherheitstests—brauchen Angreifer-Mindset. Claude ist konservativ, verfehlt nicht-standardmäßige Angriffsflächen (SQL-Keyword-Injektion, extrem lange Strings, alternative Unicodes)
  • Performance-Baselines—brauchen Zahlen aus echter Produktion. Claude rät die Schwellen
  • Große Umstellungen von Fixtures / Factories—das ist architektonisch; zurück in Plan Mode, nicht Sache des Reviews

Dort führt der Mensch und Claude assistiert.

Die Standardkonfiguration

Diese Aufteilung in einen ausführbaren Default gießen:

  1. Bevor das Feature beginnt, erkläre ich die Geschäftsregeln (nicht RSpec-Konventionen)
  2. Claude schreibt Implementierung und Tests
  3. Tests laufen. Grün = weiter. Rot = Claude repariert selbst
  4. Ich review:
    • Nicht Syntax / Mock / Factory
    • Abdeckung: ist jede Geschäftsregel durch mindestens einen Test geschützt?
    • Edge Cases verhören: „0 Zeilen / null / Concurrency / Autz-Verletzung"—einzeln durchgehen
    • Testnamen lesen—sagt der Name nicht, was getestet wird, benenne Claude um
  5. In Produktion entdeckte Bugs kommen als Regressionstests zurück—das ist der normale Verschleiß der Aufteilung, kein Scheitern

Zum Abschluss

Programmierer haben fürs Lesen von Tests weniger mentale Kraft als fürs Lesen von Code. Tests sind repetitiv, mechanisch, zehrend und notwendig. Das alles beschreibt genau Claudes Stärke—er langweilt sich nicht, ermüdet nicht, schneidet beim 50. it keine Ecken.

Deine Aufgabe ist nicht „Tests schreiben"—sondern „sicherstellen, dass jede Geschäftsregel durch einen Test gedeckt ist". Das eine ist Implementierung, das andere Urteil. Urteil bleibt bei dir; Implementierung geht an Claude.

119 Specs / 1.562 Zeilen in einem Commit geliefert und über zwei Wochen ohne Rework—das liegt nicht daran, dass ich bessere Tests schreibe, sondern daran, dass ich überhaupt keine geschrieben habe. Ich mache nur eine Sache, die Claude nicht tut: entscheiden, welche Geschäftsregeln geschützt werden müssen.