เกือบทั้ง 1,562 บรรทัดของ test ของ TopicReview เขียนโดย Claude ผมแค่ review—กว่าสองสัปดาห์บน production, commit ที่ตามมาทุกตัวเพิ่ม test ไม่มีตัวไหนเขียนใหม่ บทความนี้ว่าด้วยทำไม test คือเป้าหมายมอบหมายที่ดีที่สุด, ใน review ดูอะไรและไม่ดูอะไร (รวมถึง edge case ที่ขาดจริงใน spec) และค่าเริ่มต้นของการแบ่งงานนี้
บทความที่แล้วจบด้วย "119 spec เขียวหมด" ของ TopicReview คำถามถัดไปที่ผู้อ่านควรถามจริง ๆ: test พวกนั้นใครเขียน?
คำตอบ: Claude เขียนเกือบทั้งหมด 1,562 บรรทัดของโค้ด test ผมแค่ review มากกว่าสองสัปดาห์บน production แล้ว และรูปแบบการดูแล 1,562 บรรทัดนี้คือ เพิ่ม test ใหม่เท่านั้น ไม่เคยเขียน test เก่าใหม่
บทความนี้ว่าด้วยทำไม test คือตัวเลือกดีที่สุดสำหรับมอบหมายให้ Claude, ดูอะไรและไม่ดูอะไรใน review และการแบ่งงานนี้ไปได้ไกลแค่ไหนในทางปฏิบัติ
test ของ TopicReview อยู่ใน 7 ไฟล์:
spec/services/topic_review_service_spec.rb 760 บรรทัด (88 test)
spec/requests/topic_reviews_spec.rb 281 บรรทัด (32 test)
spec/requests/review_appeals_spec.rb 152 บรรทัด (16 test)
spec/requests/review_votes_spec.rb 127 บรรทัด
spec/policies/topic_review_policy_spec.rb 109 บรรทัด
spec/jobs/close_topic_review_job_spec.rb 71 บรรทัด (7 test)
spec/models/topic_review_spec.rb 62 บรรทัด
───────────────────────────────────────────
1,562 บรรทัด
ครอบคลุม test สี่ประเภท: service (ตรรกะธุรกิจ), request (controller + integration), policy (การอนุญาต Pundit), job (งานตามกำหนดเวลา)
commit แรก d162f1e มี Co-Authored-By: Claude Sonnet 4.6 และลงจอดด้วย 1,100+ บรรทัดจากทั้งหมดในครั้งเดียว commit spec ที่ตามมาทั้งหมดเป็น "Add test for..."—ไม่มีแม้แต่ตัวเดียวที่เป็น refactor หรือ 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
อุดช่องโหว่ ไม่ใช่ทำใหม่ รายละเอียดนี้จะย้อนกลับมาสำคัญทีหลัง
เหตุผลแข็ง 4 ข้อ:
1. input และ output ชัดเจน. test โดยเนื้อแท้คือ "ให้ state นี้ → คาดหวังพฤติกรรมนี้" นี่คือจุดแข็งที่สุดของ Claude: แปล spec เป็น assertion โค้ดธุรกิจบางครั้งต้องชั่งน้ำหนัก, test แทบไม่ต้อง
2. เชิงกลไก × ปริมาณสูง. describe .open! เพียงอันเดียวต้องครอบคลุม "มีลูกขุนที่เข้าข่าย / ไม่มี / ไม่มี topic / มี review ที่กำลัง active อยู่แล้ว"—สี่ context, แต่ละอัน 2–5 it คนเริ่มตัดมุมที่ context ที่สาม Claude เขียน it ลำดับที่ 88 ด้วยความใส่ใจเท่ากับลำดับที่ 1
3. ลูป feedback สั้นมาก. เขียน test, รัน rspec, วินาทีก็รู้ว่าผ่านหรือไม่ โค้ดธุรกิจต้องใช้งานจริงหลายวันปัญหาถึงจะโผล่ ลูปสั้น = ข้อผิดพลาดของ Claude ถูก rspec จับได้ทันที—คุณไม่ต้องเฝ้า
4. ขนานโดยธรรมชาติ. บล็อก it เป็นอิสระต่อกัน ไม่มี coupling ซ่อน ขยายได้ง่ายมาก การสร้าง test แยกกันหลายสิบตัวพร้อมกันคือจุดที่ Claude เก่ง
นี่คือแกนของการแบ่งงานทั้งหมด
ไม่ดู:
ดู:
ข้อสุดท้ายคือคุณค่าแท้ของ review Claude ครอบคลุม test "ที่เขานึกออก" แต่ test ที่เขานึกไม่ออกจะไม่เขียนขึ้นเอง นี่คือจุดที่ review ของมนุษย์เข้ามา—เดินย้อนจากกฎธุรกิจไปหาความครอบคลุมที่ขาด
เปิดส่วนต้นของ describe ".open!" ใน spec/services/topic_review_service_spec.rb:
describe ".open!" do
context "when there are eligible jurors" do
# สถานะ review ถูกต้อง / post under_review / สร้าง assignments / แจ้ง author / แจ้ง jurors / ไม่เปิดซ้ำ
end
context "when there are no eligible jurors" do
# สร้าง review แต่ไม่สร้าง assignments
end
context "when post has no topic" do
# คืน nil
end
end
ดูเหมือนครอบคลุม แต่กฎจริงของ eligible_jurors ใน model กันสามกลุ่ม:
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
ดู test ทีนี้—test ไหนยืนยันว่า "ผู้เขียน post จะไม่ถูกเลือกเป็นลูกขุน"?
ค้น service_spec.rb และ model_spec.rb: ไม่มี model_spec.rb แค่ทดสอบไม่กี่เคสของ scope pending_vote_by ไม่ได้ครอบคลุม eligible_jurors โดยตรง service_spec.rb มีแค่คอมเมนต์เดียว: # Jurors must NOT be the post author—อยู่ใน setup ไม่ใช่ assertion
นี่คือสิ่งที่ review จับได้: กฎการกันสามข้อ (ผู้เขียน / ผู้รายงาน / เคยโหวตใน initial แล้ว) ไม่มีข้อใดได้รับการปกป้องโดย test ถ้าใครสักคน refactor eligible_jurors ทีหลังแล้วพลั้งหลุด post.user_id จาก list กันออก test เดิมทุกตัวจะผ่าน—และ production จะปล่อยให้ผู้เขียนนั่งในคณะลูกขุนของตัวเองอย่างเงียบ ๆ
Claude ไม่ผิด—เขาทดสอบตามที่ขอ เพียงแต่ไม่ถามเองว่า "กฎแต่ละข้อใน 3 ข้อนี้ต้องการ test ปกป้องไหม?" คำถามนั้น—จากกฎย้อนไปหาความครอบคลุม—คืองานของ review
(พูดตรง ๆ: ผมเองก็พลาดอันนี้ใน review แรก ไปเห็นเอาตอน audit ครั้งที่สองระหว่างเขียนบทความนี้ ดังนั้น review ก็ไม่ใช่ one-shot เหมือนกัน—แต่ก็ยัง ดีกว่าไม่ review 10 เท่า)
ถ้า "Claude เขียน + มนุษย์ review" สมบูรณ์แบบ หลัง commit แรกจะไม่มี commit test ใหม่อีก สิ่งที่เกิดขึ้นจริงน่าสนใจกว่า—อุดช่องโหว่แต่ไม่เขียนใหม่:
00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning
อันแรกเป็น regression test หลังบั๊ก—e8cb2db Default to keep verdict when review expires with zero votes คือ fix, 00393fc คือ test ที่คู่กัน รูปแบบเดียวกันกับอันที่สอง ตามหลัง abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes
สอง commit นี้พิสูจน์สองเรื่องพร้อมกัน:
"ดีพอ + ปะแก้ต่อได้เรื่อย ๆ" เป็นเกณฑ์ที่สมจริงกว่า "สมบูรณ์แบบ" มาก การวิ่งตาม review ที่สมบูรณ์แบบคือสิ่งที่หยุดคุณจากการมอบหมาย test ให้ Claude การยอมรับ "ดีพอ" คือสิ่งที่ขับเคลื่อนการแบ่งงานนี้
ไม่ใช่ทุก test เหมาะกับ handoff เต็ม:
ในกรณีเหล่านี้ มนุษย์เป็นผู้นำ Claude ช่วย
แปลงการแบ่งงานนี้เป็น default ที่ทำได้:
โปรแกรมเมอร์มีทรัพยากรทางใจสำหรับอ่าน test น้อยกว่าอ่านโค้ด test ซ้ำซาก, เชิงกลไก, เหนื่อยใจ แต่จำเป็น ทั้งหมดนั้นตรงกับ sweet spot ของ Claude—ไม่เบื่อ ไม่เหนื่อย ไม่ตัดมุมที่ it ลำดับ 50
งานของคุณไม่ใช่ "เขียน test"—แต่คือ "ทำให้กฎธุรกิจทุกข้อถูก test ครอบคลุม" ด้านหนึ่งคือ implementation อีกด้านคือการตัดสินใจ การตัดสินใจอยู่กับคุณ, implementation ไปหา Claude
119 spec / 1,562 บรรทัด ส่งขึ้นใน commit เดียวและอยู่บน production กว่าสองสัปดาห์โดยไม่ต้อง rework—ไม่ใช่เพราะผมเขียน test ดีกว่า แต่เพราะ ผมไม่ได้เขียนเลย ผมแค่ทำสิ่งเดียวที่ Claude ไม่ทำ: ตัดสินใจว่ากฎธุรกิจข้อไหนสมควรได้รับการปกป้อง