Free

Biarkan Claude Menulis Test, Kamu Cukup Review: 1.562 Baris di Lapangan

Hampir seluruh 1.562 baris test TopicReview ditulis Claude. Saya hanya review — lebih dari dua minggu di produksi, semua commit lanjutan menambah test, tak ada yang menulis ulang. Tulisan ini soal kenapa test adalah target ideal untuk didelegasikan, apa yang dilihat dan diabaikan saat review (termasuk satu edge case nyata yang hilang di spec), dan konfigurasi default pembagian ini.


Tulisan sebelumnya ditutup dengan "119 spec hijau" untuk TopicReview. Pertanyaan berikutnya yang wajar ditanyakan: siapa yang menulis test itu?

Jawaban: Claude menulis hampir semua 1.562 baris kode test. Saya hanya review. Sudah lebih dua minggu di produksi, dan pola perawatan 1.562 baris itu adalah hanya menambah test baru, tak pernah menulis ulang yang lama.

Tulisan ini soal kenapa test adalah target delegasi terbaik untuk Claude, apa yang dilihat dan diabaikan saat review, dan sejauh mana pembagian ini bertahan di lapangan.

Turunkan angkanya dulu

Test TopicReview tersebar di 7 file:

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

Meliputi empat jenis test: service (logika bisnis), request (controller + integrasi), policy (otorisasi Pundit), job (tugas terjadwal).

Commit awal d162f1e membawa Co-Authored-By: Claude Sonnet 4.6 dan mendaratkan 1.100+ baris itu sekaligus. Semua commit terkait spec setelahnya adalah "Add test for..."—tak ada satu pun refactor atau 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

Menambal celah, bukan mengulang. Detail ini akan penting lagi nanti.

Kenapa test adalah kandidat delegasi terbaik

Empat alasan keras:

1. Input dan output eksplisit. Test pada hakikatnya "diberi state ini → harapkan perilaku ini". Itu justru kekuatan Claude: menerjemahkan spesifikasi ke assertion. Kode bisnis kadang butuh pertimbangan, test tidak.

2. Mekanis × volume tinggi. Satu describe .open! harus mencakup "ada juri / tak ada juri / tak ada topic / review sudah aktif"—empat context, masing-masing 2–5 it. Manusia mulai memotong di context ketiga. Claude menulis it ke-88 dengan ketelitian yang sama seperti yang pertama.

3. Loop umpan balik sangat pendek. Tulis test, jalankan rspec, hitungan detik tahu lulus atau tidak. Kode bisnis butuh hari pemakaian nyata untuk memunculkan masalah. Loop pendek = kesalahan Claude langsung ditangkap rspec—kamu tak perlu mengawasi.

4. Paralel secara alamiah. Blok it saling independen, tak ada kopling tersembunyi, mudah diskalakan. Menghasilkan puluhan test terisolasi sekaligus persis yang Claude kuasai.

Apa yang dilihat dan diabaikan saat review

Ini poros dari seluruh pembagian.

Abaikan:

  • Apakah sintaks RSpec benar → Claude nyaris tak pernah salah
  • Kualitas mock → kecuali jelas over-mock, umumnya oke
  • Estetika factory → tak penting, yang penting jalan
  • Konsistensi gaya → kalau ada yang salah, minta Claude benahi semua sekaligus

Lihat:

  • Apakah edge case benar-benar tercakup
  • Apakah nama test memang menggambarkan perilaku yang diharapkan
  • Apakah ada test yang seharusnya ada tapi tidak ditulis

Poin terakhir adalah nilai sejati review. Claude mencakup test "yang terpikir olehnya", tapi test yang tak terpikir olehnya tidak ditulis otomatis. Di situlah review manusia berperan—mundur dari aturan bisnis ke cakupan yang hilang.

Contoh konkret: apa yang sungguh-sungguh ditangkap review

Buka awal describe ".open!" di spec/services/topic_review_service_spec.rb:

describe ".open!" do
  context "when there are eligible jurors" do
    # status review benar / post under_review / assignments dibuat / author di-notify / jurors di-notify / tak dobel open
  end
  context "when there are no eligible jurors" do
    # review dibuat tapi assignments tidak
  end
  context "when post has no topic" do
    # kembalikan nil
  end
end

Kelihatannya komprehensif. Tapi aturan nyata eligible_jurors di model mengecualikan tiga kelompok:

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

Sekarang lihat test—test mana yang menyatakan "penulis post tak akan dipilih sebagai juri"?

Telusuri service_spec.rb dan model_spec.rb: tak ada. model_spec.rb hanya menguji beberapa kasus scope pending_vote_by; tak menyentuh eligible_jurors langsung. service_spec.rb hanya punya satu komentar: # Jurors must NOT be the post author—itu komentar setup, bukan assertion.

Inilah yang bisa ditangkap review: tiga aturan pengecualian (penulis / pelapor / sudah-vote-inisial), tak satu pun dilindungi test. Kalau nanti seseorang me-refactor eligible_jurors dan tanpa sengaja menghapus post.user_id dari daftar pengecualian, semua test yang ada lolos—dan produksi diam-diam membiarkan penulis duduk di kursi jurinya sendiri.

Claude tidak salah—ia menguji yang diminta diuji. Ia hanya tak menanyakan sendiri: "apakah masing-masing dari tiga aturan ini perlu cakupan test?" Pertanyaan itu—dari aturan balik ke cakupan—itulah yang dikerjakan review.

(Jujur saja: saya juga melewatkan ini di review awal. Baru tertangkap saat audit kedua sambil menulis posting ini. Jadi review pun bukan one-shot—tapi tetap 10× lebih baik daripada tanpa review.)

Commit-commit berikutnya membuktikan pembagian ini berjalan di lapangan

Kalau "Claude menulis + manusia review" sempurna, tak akan ada commit test baru setelah yang pertama. Realitasnya lebih menarik—menambal tanpa menulis ulang:

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

Yang pertama adalah regression test pasca-bug—e8cb2db Default to keep verdict when review expires with zero votes adalah fix, 00393fc adalah test pelengkapnya. Pola yang sama untuk yang kedua, mengekor abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.

Dua commit ini membuktikan dua hal sekaligus:

  • Review tidak menangkap 100% kasus—karena itu produksi memunculkan dua bug
  • Tapi arsitektur test cukup stabil, kami bisa terus menambahkan test tanpa restrukturisasi—karena itu commit berbunyi "Add test for..." dan bukan "Rewrite ... spec"

"Cukup baik + bisa terus ditambal" adalah standar yang jauh lebih realistis daripada "sempurna". Mengejar review sempurna justru menghentikan kamu mendelegasikan test ke Claude. Menerima 'cukup baik' yang membuat pembagian ini berjalan.

Test yang sebaiknya tidak didelegasikan penuh ke Claude

Tak semua test cocok untuk handoff penuh:

  • E2E happy-path—butuh kacamata produk. Claude bisa menulis, tapi cenderung hanya menutupi "secara teknis lolos" dan kehilangan "di mana pengguna nyatanya tersangkut"
  • Test keamanan—butuh pola pikir penyerang. Claude konservatif dan kehilangan permukaan serangan non-standar (injeksi kata kunci SQL, string sangat panjang, unicode alternatif)
  • Baseline performa—butuh angka dari lingkungan nyata. Claude menebak ambang
  • Perombakan besar fixture / factory—itu perubahan level arsitektur; kembali ke plan mode, bukan yang ditangkap review

Dalam kasus ini manusia memimpin dan Claude membantu.

Konfigurasi default

Menjadikan pembagian ini default yang bisa dijalankan:

  1. Sebelum fitur mulai, saya jelaskan aturan bisnis (bukan konvensi RSpec)
  2. Claude menulis implementasi dan test
  3. Jalankan test. Lulus = lanjut. Gagal = Claude perbaiki sendiri
  4. Saya review:
    • Tak melihat sintaks / mock / factory
    • Melihat cakupan: apakah setiap aturan bisnis dilindungi setidaknya satu test
    • Menginterogasi edge case: "0 baris / null / konkurensi / pelanggaran otz"—satu per satu
    • Membaca nama test—kalau dari nama tak bisa menebak yang diuji, minta Claude mengganti nama
  5. Bug yang ditemukan di produksi kembali sebagai regression test—itu keausan normal pembagian, bukan kegagalan

Penutup

Programmer punya energi mental lebih sedikit untuk membaca test daripada membaca kode. Test repetitif, mekanis, melelahkan, tapi perlu. Semua itu persis titik kuat Claude—tak bosan, tak lelah, tak memotong jalan di it ke-50.

Pekerjaanmu bukan "menulis test"—tapi "memastikan setiap aturan bisnis tercakup oleh test". Satu adalah implementasi, satu lagi penilaian. Penilaian tinggal padamu; implementasi pergi ke Claude.

119 spec / 1.562 baris ship dalam satu commit dan bertahan dua minggu lebih tanpa rework—bukan karena saya menulis test lebih baik, tapi karena saya sama sekali tidak menulis. Saya hanya melakukan satu hal lebih dari Claude: memutuskan aturan bisnis mana yang layak dilindungi.