Free

Lascia che Claude scriva i test, tu fai solo la review: 1.562 righe alla prova

Quasi tutte le 1.562 righe di test di TopicReview le ha scritte Claude. Io faccio solo la review — oltre due settimane in produzione, tutti i commit successivi aggiungono test, nessuno li riscrive. Questo post tratta del perché i test sono il bersaglio ideale da delegare, cosa guardare e cosa ignorare in review (incluso un edge case realmente mancante nello spec), e la configurazione di default di questa ripartizione.


Il post precedente chiudeva con «119 spec in verde» per TopicReview. La vera domanda successiva: chi ha scritto quei test?

Risposta: Claude ha scritto quasi tutte le 1.562 righe di codice di test. Io faccio solo review. Oltre due settimane in produzione, e il pattern di manutenzione di quelle 1.562 righe è solo aggiungere nuovi test, mai riscrivere i vecchi.

Questo post parla del perché i test sono il miglior candidato da delegare a Claude, cosa guardare e cosa ignorare in review, e fino a dove regge questa ripartizione nella pratica.

Prima i numeri

I test di TopicReview sono distribuiti su 7 file:

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

Quattro tipi di test coperti: service (logica di business), request (controller + integrazione), policy (autorizzazione Pundit), job (task pianificati).

Il commit iniziale d162f1e porta Co-Authored-By: Claude Sonnet 4.6 e atterra con 1.100+ di quelle righe in un colpo. Ogni commit di spec successivo è «Add test for...»—neppure uno è refactor o 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

Tappare buchi, non rifare. Questo dettaglio torna dopo.

Perché i test sono il miglior candidato da delegare

Quattro ragioni dure:

1. Input e output espliciti. Un test è «dato questo stato → aspettati questo comportamento». È il punto forte di Claude: tradurre una spec in asserzione. Il codice di business richiede talvolta compromessi; i test quasi mai.

2. Meccanico × grande volume. Un singolo describe .open! deve coprire «con giurati ammissibili / senza / senza topic / review già attiva»—quattro context, con 2–5 it ciascuno. L'umano inizia a tagliare gli angoli al terzo context. Claude scrive l'88° it con la stessa cura del primo.

3. Ciclo di feedback brevissimo. Scrivi un test, lanci rspec, in secondi sai se passa. Il codice di business richiede giorni di uso reale prima che i problemi emergano. Ciclo breve = qualsiasi errore di Claude viene beccato da rspec sul posto—non serve sorvegliare.

4. Naturalmente parallelo. I blocchi it sono indipendenti, senza accoppiamento nascosto, scalano in modo banale. Generare decine di test isolati in un colpo è esattamente il punto forte di Claude.

Cosa guardare in review, cosa ignorare

Qui sta il perno dell'intera ripartizione.

Ignorare:

  • Se la sintassi RSpec è corretta → Claude non sbaglia quasi mai
  • Qualità del mock → a meno di over-mock evidente, va bene
  • Estetica delle factory → non importa, se gira gira
  • Consistenza di stile → se c'è qualcosa fuori, chiedi a Claude di sistemare tutto in un colpo

Guardare:

  • Se gli edge case sono davvero coperti
  • Se i nomi dei test descrivono il comportamento atteso reale
  • Se c'è qualche test che dovrebbe esistere e manca

L'ultimo punto è il valore vero della review. Claude copre i test «che gli vengono in mente», ma quelli che non gli vengono in mente non si scrivono da soli. È esattamente lì che si incastra la review umana—risalire dalle regole di business alla copertura mancante.

Un esempio concreto: cosa la review prende davvero

Apri l'inizio del describe ".open!" in spec/services/topic_review_service_spec.rb:

describe ".open!" do
  context "when there are eligible jurors" do
    # stato review ok / post under_review / assignments create / author notificato / jurors notificati / nessun doppio open
  end
  context "when there are no eligible jurors" do
    # review creata ma non assignments
  end
  context "when post has no topic" do
    # restituisce nil
  end
end

Sembra completo. Ma la vera regola di eligible_jurors nel model esclude tre gruppi:

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

Ora guarda i test—quale test afferma che «l'autore del post non è mai selezionato come giurato»?

Cerca in service_spec.rb e model_spec.rb: niente. model_spec.rb testa solo alcuni casi dello scope pending_vote_by; non copre eligible_jurors direttamente. In service_spec.rb c'è solo un commento: # Jurors must NOT be the post author—è nel setup, non un'asserzione.

Ecco cosa la review prende: tre regole di esclusione (autore / segnalante / già-votato-in-initial), e nessuna è protetta da un test. Se in seguito qualcuno refattorizza eligible_jurors e per sbaglio toglie post.user_id dalla lista di esclusione, tutti i test esistenti passano—e la produzione lascia silenziosamente che gli autori siedano nella propria giuria.

Claude non ha sbagliato—ha testato quel che gli è stato chiesto. Semplicemente non ha chiesto da solo: «ciascuna di queste tre regole ha bisogno di copertura di test?» Quella domanda—dalle regole alla copertura a ritroso—è il lavoro della review.

(Onestà: anch'io l'ho mancato nella review iniziale. L'ho notato solo in un secondo audit mentre scrivevo questo post. Dunque anche la review non è one-shot—ma resta comunque 10× meglio che non fare review.)

I commit successivi dimostrano che la ripartizione regge in pratica

Se «Claude scrive + umano rivede» fosse perfetto, non ci sarebbero commit di test dopo quello iniziale. Quel che è successo è più interessante—toppare buchi senza riscritture:

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

Il primo è un regression test post-bug—e8cb2db Default to keep verdict when review expires with zero votes è il fix, 00393fc è il test abbinato. Stesso pattern per il secondo, a ruota di abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.

Questi due commit dimostrano due cose insieme:

  • La review non ha preso il 100% dei casi—perciò la produzione ha esposto due bug
  • Ma l'architettura dei test ha retto; abbiamo potuto continuare ad aggiungere test senza ristrutturare—perciò i commit dicono «Add test for...» e non «Rewrite ... spec»

«Abbastanza buono + rimediabile continuativamente» è un'asticella molto più realistica di «perfetto». Inseguire la review perfetta è proprio ciò che ti impedisce di delegare i test a Claude. Accettare l'«abbastanza buono» è ciò che mette in moto la ripartizione.

Test che non vanno delegati completamente a Claude

Non tutti i test sono adatti a un handoff totale:

  • Happy-path E2E—servono occhi da prodotto. Claude può scriverli ma tende a coprire «tecnicamente arrivabile in fondo» e si perde «dove l'utente davvero si incastra»
  • Test di sicurezza—serve mentalità da attaccante. Claude è conservativo, si perde superfici d'attacco non standard (injection di parole chiave SQL, stringhe smisurate, unicode alternativo)
  • Baseline di performance—servono numeri da ambiente reale. Claude tira a caso le soglie
  • Grandi ristrutturazioni di fixture / factory—è livello architetturale; torna in plan mode, non è qualcosa che la review cattura

In questi casi guida l'umano e Claude assiste.

La configurazione di default

Trasformare questa ripartizione in un default eseguibile:

  1. Prima che la feature parta, spiego le regole di business (non le convenzioni RSpec)
  2. Claude scrive implementazione e test
  3. Lancio i test. Passa = proseguo. Fallisce = Claude ripara da solo
  4. Faccio review:
    • Non sintassi / mock / factory
    • Copertura: ogni regola di business è protetta da almeno un test?
    • Interrogo gli edge case: «0 righe / null / concorrenza / violazione autz»—uno alla volta
    • Leggo i nomi dei test—se il nome non mi dice cosa si testa, chiedo a Claude di rinominarlo
  5. I bug scoperti in produzione tornano come regression test—è l'usura normale della ripartizione, non un fallimento

Chiudendo

Un programmatore ha meno risorse mentali per leggere test che per leggere codice. I test sono ripetitivi, meccanici, logoranti, necessari. Tutto ciò descrive perfettamente il punto forte di Claude—non si annoia, non si stanca, non taglia gli angoli all'it numero 50.

Il tuo lavoro non è «scrivere test»—è «assicurarti che ogni regola di business sia coperta da un test». Uno è implementazione, l'altro è giudizio. Il giudizio resta a te; l'implementazione va a Claude.

119 spec / 1.562 righe consegnate in un commit e rimaste in piedi oltre due settimane senza rework—non è perché scrivo meglio i test, ma perché non ne ho scritto nessuno. Faccio solo una cosa che Claude non fa: decido quali regole di business meritano protezione.