Presque la totalité des 1 562 lignes de tests de TopicReview ont été écrites par Claude. Je ne fais que la review — plus de deux semaines en production, tous les commits suivants ajoutent des tests, aucun ne les réécrit. Cet article explique pourquoi les tests sont la cible idéale à déléguer, ce qu'il faut regarder et ignorer en review (y compris un edge case réellement manquant dans le spec), et la configuration par défaut de ce partage.
Le billet précédent se terminait sur « 119 specs au vert » pour TopicReview. La vraie question qui suit : qui a écrit ces tests ?
Réponse : Claude a écrit la quasi-totalité des 1 562 lignes de code de test. Je ne fais que la review. Plus de deux semaines en production, et le schéma de maintenance de ces 1 562 lignes, c'est ajouter uniquement de nouveaux tests, jamais réécrire les anciens.
Ce billet porte sur ce qui fait des tests la meilleure chose à déléguer à Claude, ce qu'on regarde et ce qu'on ignore en review, et jusqu'où ce partage tient dans la pratique.
Les tests de TopicReview se répartissent sur 7 fichiers :
spec/services/topic_review_service_spec.rb 760 lignes (88 tests)
spec/requests/topic_reviews_spec.rb 281 lignes (32 tests)
spec/requests/review_appeals_spec.rb 152 lignes (16 tests)
spec/requests/review_votes_spec.rb 127 lignes
spec/policies/topic_review_policy_spec.rb 109 lignes
spec/jobs/close_topic_review_job_spec.rb 71 lignes (7 tests)
spec/models/topic_review_spec.rb 62 lignes
───────────────────────────────────────────
1 562 lignes
Quatre types de tests : service (logique métier), request (controller + intégration), policy (autorisation Pundit), job (tâches planifiées).
Le commit initial d162f1e porte Co-Authored-By: Claude Sonnet 4.6 et atterrit avec plus de 1 100 de ces lignes en une fois. Tous les commits de spec qui suivent sont « Add test for... »—aucun n'est un refactor ou une 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
Bouchage de trous, pas reprise. Ce détail revient plus loin.
Quatre raisons dures :
1. Entrées et sorties explicites. Un test, c'est « étant donné cet état → attendre ce comportement ». C'est le point fort de Claude : traduire une spec en assertion. Le code métier demande parfois des arbitrages ; les tests, presque jamais.
2. Mécanique × gros volume. Un seul describe .open! doit couvrir « avec jurés éligibles / sans jurés / sans topic / review déjà active »—quatre contexts, 2 à 5 it chacun. L'humain commence à couper les angles au troisième context. Claude écrit le 88ᵉ it avec le même soin que le premier.
3. Boucle de feedback ultra-courte. Tu écris un test, tu lances rspec, en quelques secondes tu sais s'il passe. Le code métier prend des jours d'usage réel avant de cracher ses problèmes. Boucle courte = la moindre erreur de Claude est attrapée sur place par rspec—pas besoin de surveiller.
4. Parallèle par nature. Les blocs it sont indépendants, sans couplage caché, se multiplient trivialement. Générer des dizaines de tests isolés d'un coup, c'est exactement là que Claude brille.
C'est le pivot de tout le partage.
Ignorer :
Regarder :
Le dernier point est la vraie valeur de la review. Claude couvre les tests « qui lui viennent à l'esprit », mais ceux qui ne lui viennent pas ne s'écrivent pas tout seuls. C'est exactement là que la review humaine s'emboîte—remonter des règles métier vers la couverture manquante.
Ouvrez le début du describe ".open!" dans spec/services/topic_review_service_spec.rb :
describe ".open!" do
context "when there are eligible jurors" do
# statut review ok / post under_review / assignments créées / author notifié / jurors notifiés / pas de double open
end
context "when there are no eligible jurors" do
# review créée mais pas d'assignments
end
context "when post has no topic" do
# retourne nil
end
end
Ça a l'air complet. Mais la vraie règle de eligible_jurors dans le model exclut trois groupes :
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
Regardez les tests—quel test affirme que « l'auteur du post n'est jamais sélectionné comme juré » ?
On fouille service_spec.rb et model_spec.rb : rien. model_spec.rb ne teste que quelques cas du scope pending_vote_by ; il ne couvre pas eligible_jurors directement. service_spec.rb n'a qu'un commentaire : # Jurors must NOT be the post author—c'est dans le setup, pas une assertion.
Voilà ce que la review attrape : trois règles d'exclusion (auteur / signaleur / déjà-voté-initial), aucune protégée par un test. Si plus tard quelqu'un refactorise eligible_jurors et laisse tomber par mégarde post.user_id de la liste d'exclusion, tous les tests existants passent—et la prod laisse silencieusement les auteurs siéger à leur propre jury.
Claude n'avait pas tort—il a testé ce qu'on lui a demandé de tester. Il n'a simplement pas demandé de lui-même : « chacune de ces trois règles a-t-elle besoin de sa couverture ? » Cette question—remonter des règles à la couverture—c'est le boulot de la review.
(Honnêtement : j'ai moi-même raté ça à la première review. Je ne l'ai vu qu'à un second audit en écrivant ce billet. Donc la review non plus n'est pas one-shot—mais ça reste 10× mieux que de ne pas relire.)
Si « Claude écrit + humain relit » était parfait, il n'y aurait pas de commit de test après l'initial. La réalité est plus intéressante—bouchage de trous, sans réécriture :
00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning
Le premier est un regression test post-bug—e8cb2db Default to keep verdict when review expires with zero votes est le fix, 00393fc le test associé. Même schéma pour le second, en suivant abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.
Ces deux commits prouvent deux choses à la fois :
« Suffisamment bon + on peut continuer à rustiner » est une barre bien plus réaliste que « parfait ». Courir après la review parfaite, c'est ce qui t'empêche de déléguer les tests à Claude. Accepter le « suffisamment bon » est ce qui met le partage en route.
Tous les tests ne conviennent pas à un handoff total :
Dans ces cas-là, l'humain mène et Claude assiste.
Transformer ce partage en default exécutable :
Un dev a moins de ressource mentale pour lire des tests que pour lire du code. Les tests sont répétitifs, mécaniques, crevants et indispensables. Tout cela décrit pile le terrain fort de Claude—il ne s'ennuie pas, ne se fatigue pas, ne coupe pas les angles au 50ᵉ it.
Ton boulot n'est pas « écrire les tests »—c'est « garantir que chaque règle métier est couverte par un test ». Un côté implémentation, un côté jugement. Le jugement te reste ; l'implémentation part chez Claude.
119 specs / 1 562 lignes livrées en un commit et tenant plus de deux semaines sans reprise—ce n'est pas parce que j'écris mieux les tests, c'est parce que je n'en ai écrit aucun. Je fais juste une chose que Claude ne fait pas : décider quelles règles métier méritent protection.