Free

Niech Claude pisze testy, ty tylko robisz review: 1562 linie w praktyce

Niemal wszystkie 1562 linie testów TopicReview napisał Claude. Ja robię tylko review — ponad dwa tygodnie na produkcji, wszystkie kolejne commity dodają testy, żaden ich nie przepisuje. Ten wpis traktuje o tym, dlaczego testy to idealny cel do delegowania, na co patrzeć i co pomijać w review (łącznie z realnie brakującym edge case w specu) i o domyślnej konfiguracji tego podziału.


Poprzedni wpis zamknęliśmy zdaniem „119 speców na zielono" dla TopicReview. Prawdziwe następne pytanie: kto napisał te testy?

Odpowiedź: Claude napisał niemal wszystkie 1562 linie kodu testów. Ja tylko robię review. Ponad dwa tygodnie na produkcji, a wzorzec utrzymania tych 1562 linii to tylko dodawanie nowych testów, nigdy przepisywanie starych.

Ten wpis dotyczy tego, dlaczego testy są najlepszym kandydatem do delegowania Claude'owi, na co patrzeć i co pomijać w review, i jak daleko ten podział faktycznie działa.

Najpierw liczby

Testy TopicReview są rozrzucone w 7 plikach:

spec/services/topic_review_service_spec.rb   760 linii (88 testów)
spec/requests/topic_reviews_spec.rb          281 linii (32 testy)
spec/requests/review_appeals_spec.rb         152 linie (16 testów)
spec/requests/review_votes_spec.rb           127 linii
spec/policies/topic_review_policy_spec.rb    109 linii
spec/jobs/close_topic_review_job_spec.rb      71 linii (7 testów)
spec/models/topic_review_spec.rb              62 linie
───────────────────────────────────────────
                                          1 562 linie

Cztery typy testów objęte: service (logika biznesowa), request (controller + integracja), policy (autoryzacja Pundit), job (zadania planowe).

Początkowy commit d162f1e nosi Co-Authored-By: Claude Sonnet 4.6 i ląduje z 1100+ tych linii za jednym zamachem. Każdy kolejny commit do speców to „Add test for..."—ani jeden refactor ani 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

Łatanie dziur, nie przerabianie. Ten szczegół wraca później.

Dlaczego testy to najlepszy kandydat do delegowania

Cztery twarde powody:

1. Wejścia i wyjścia są jawne. Test to w istocie „dany ten stan → oczekuj tego zachowania". To mocna strona Claude'a: przetłumaczyć specyfikację na assercję. Kod biznesowy czasem wymaga kompromisów; testy prawie nigdy.

2. Mechaniczne × duża ilość. Pojedynczy describe .open! musi pokryć „są uprawnieni przysięgli / brak / brak topic / aktywny review już istnieje"—cztery context, 2–5 it w każdym. Człowiek zaczyna ucinać rogi przy trzecim context. Claude pisze 88. it z tą samą starannością co pierwszy.

3. Bardzo krótka pętla feedbacku. Piszesz test, odpalasz rspec, w sekundach wiesz czy przechodzi. Kod biznesowy wymaga dni prawdziwego użycia, żeby problemy wypłynęły. Krótka pętla = każdy błąd Claude'a łapie rspec na miejscu—nie musisz pilnować.

4. Naturalnie równoległe. Bloki it są niezależne, bez ukrytych sprzężeń, skalują się trywialnie. Wygenerowanie dziesiątek izolowanych testów od razu to dokładnie to, w czym Claude jest dobry.

Na co patrzeć w review, co ignorować

To punkt zwrotny całego podziału.

Ignorować:

  • Czy składnia RSpec jest poprawna → Claude prawie nigdy się nie myli
  • Jakość mocka → o ile nie ma oczywistego over-mock-a, jest ok
  • Estetykę factory → nieistotne, działa znaczy działa
  • Spójność stylu → jeśli coś odstaje, Claude jedną instrukcją naprawi wszystko

Patrzeć:

  • Czy edge case'y są naprawdę pokryte
  • Czy nazwy testów opisują rzeczywiste oczekiwane zachowanie
  • Czy brakuje testu, który powinien być

Ostatni punkt to prawdziwa wartość review. Claude pokrywa testy „które mu przychodzą do głowy", ale te, które mu nie przychodzą, same się nie napiszą. Dokładnie tutaj wpisuje się ludzkie review—iść od tyłu od reguł biznesowych do brakującego pokrycia.

Konkretny przykład: co review naprawdę łapie

Otwórz początek describe ".open!" w spec/services/topic_review_service_spec.rb:

describe ".open!" do
  context "when there are eligible jurors" do
    # status review ok / post under_review / assignments utworzone / author powiadomiony / jurors powiadomieni / brak podwójnego open
  end
  context "when there are no eligible jurors" do
    # review utworzone ale assignments nie
  end
  context "when post has no topic" do
    # zwraca nil
  end
end

Wygląda kompleksowo. Ale prawdziwa reguła eligible_jurors w modelu wyłącza trzy grupy:

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

Teraz spójrz na testy—który test twierdzi, że „autor posta nigdy nie jest wybierany do ławy"?

Przeszukaj service_spec.rb i model_spec.rb: nic. model_spec.rb testuje tylko kilka przypadków scope'a pending_vote_by; eligible_jurors bezpośrednio nie obejmuje. W service_spec.rb jest tylko komentarz: # Jurors must NOT be the post author—to w setupie, nie assercja.

Oto co review może wyłapać: trzy reguły wykluczenia (autor / zgłaszający / już-głosował-w-initial), żadna nie chroniona testem. Jeśli ktoś później refaktoryzuje eligible_jurors i przypadkiem zrzuci post.user_id z listy wykluczeń, wszystkie istniejące testy przejdą—a produkcja cicho pozwoli autorom zasiąść we własnej ławie.

Claude się nie mylił—przetestował to, o co go poproszono. Po prostu sam nie zapytał: „czy każda z tych trzech reguł potrzebuje pokrycia testem?" To pytanie—od reguł do pokrycia wstecznie—jest pracą review.

(Uczciwie: sam to przeoczyłem w początkowym review. Zauważyłem dopiero przy drugim audycie pisząc ten wpis. Więc review też nie jest one-shot—ale wciąż 10× lepsze niż brak review.)

Kolejne commity potwierdzają, że ten podział działa w praktyce

Gdyby „Claude pisze + człowiek robi review" było idealne, po początkowym commicie nie byłoby nowych commitów testowych. Rzeczywistość jest ciekawsza—łatanie dziur bez przepisywania:

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

Pierwszy to regression test po bugu—e8cb2db Default to keep verdict when review expires with zero votes to fix, 00393fc to parujący test. Ten sam wzorzec dla drugiego, w ślad za abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.

Te dwa commity dowodzą dwóch rzeczy naraz:

  • Review nie złapało 100% przypadków—dlatego produkcja odsłoniła dwa bugi
  • Ale architektura testów utrzymała się; mogliśmy dalej dokładać testy bez restrukturyzacji—dlatego commity mówią „Add test for..." a nie „Rewrite ... spec"

„Wystarczająco dobrze + da się dalej łatać" to znacznie bardziej realistyczna poprzeczka niż „idealnie". Gonienie za idealnym review to właśnie to, co powstrzymuje cię przed oddaniem testów Claude'owi. Akceptacja „wystarczająco dobrze" uruchamia podział.

Testy, których nie powinno się w całości oddawać Claude'owi

Nie każdy test nadaje się na pełny handoff:

  • Happy-path E2E—wymagają produktowej soczewki. Claude napisze, ale skłania się do pokrywania „technicznie da się ukończyć" i gubi „gdzie użytkownik naprawdę utyka"
  • Testy bezpieczeństwa—wymagają mentalności atakującego. Claude jest zachowawczy, gubi niestandardowe powierzchnie ataku (wstrzyknięcie słów kluczowych SQL, ekstremalnie długie stringi, alternatywne unicode)
  • Bazowe linie wydajności—wymagają liczb z rzeczywistego środowiska wdrożeniowego. Claude strzela progami na oślep
  • Duże przebudowy fixture / factory—to poziom architektoniczny; wróć do plan mode, to nie jest coś, co review łapie

W tych przypadkach prowadzi człowiek, a Claude asystuje.

Konfiguracja domyślna

Zamienić ten podział w wykonywalny default:

  1. Zanim feature ruszy, wyjaśniam reguły biznesowe (nie konwencje RSpec)
  2. Claude pisze implementację i testy
  3. Odpalamy testy. Przechodzi = dalej. Pada = Claude sam naprawia
  4. Ja robię review:
    • Nie patrzę na składnię / mock / factory
    • Patrzę na pokrycie: czy każda reguła biznesowa jest chroniona co najmniej jednym testem?
    • Przesłuchuję edge case'y: „0 wierszy / null / współbieżność / naruszenie autz"—jeden po drugim
    • Czytam nazwy testów—jeśli z nazwy nie można zgadnąć, co jest testowane, niech Claude zmieni nazwę
  5. Bugi znalezione w produkcji wracają jako regression testy—to normalne zużycie podziału, nie porażka

Zamknięcie

Programista ma mniej zasobu mentalnego na czytanie testów niż na czytanie kodu. Testy są repetytywne, mechaniczne, wyczerpujące i konieczne. Wszystko to opisuje dokładnie mocną stronę Claude'a—nie nudzi się, nie męczy, nie ścina rogów przy 50. it.

Twoja praca to nie „pisanie testów"—to „upewnienie się, że każda reguła biznesowa jest pokryta testem". Jedno to implementacja, drugie to osąd. Osąd zostaje przy tobie; implementacja idzie do Claude'a.

119 speców / 1562 linie wypuszczone w jednym commicie i żyjące ponad dwa tygodnie bez rework—to nie dlatego, że lepiej piszę testy, tylko dlatego, że wcale żadnego nie napisałem. Robię jedną rzecz, której Claude nie robi: decyduję, które reguły biznesowe zasługują na ochronę.