Free

תן ל-Claude לכתוב את הבדיקות, ואתה רק עושה review: 1,562 שורות בפועל

כמעט כל 1,562 שורות הבדיקות של TopicReview נכתבו על ידי Claude. אני רק עושה review — מעל שבועיים בפרודקשן, כל הקומיטים הבאים מוסיפים בדיקות, אף אחד לא כותב אותן מחדש. הפוסט עוסק בלמה בדיקות הן יעד האצלה אידיאלי, במה להסתכל ומה להתעלם ממנו ב-review (כולל edge case אמיתי שחסר בספק), ובהגדרת ברירת המחדל של החלוקה הזו.


הפוסט הקודם ננעל במשפט "119 ספקים ירוקים" עבור TopicReview. השאלה האמיתית שבאה אחריה: מי כתב את הבדיקות האלו?

תשובה: Claude כתב כמעט את כל 1,562 שורות קוד הבדיקה. אני רק עושה review. מעל שבועיים על פרודקשן, ודפוס התחזוקה של 1,562 השורות האלו הוא רק להוסיף בדיקות חדשות, אף פעם לא לכתוב מחדש את הישנות.

הפוסט הזה עוסק בלמה בדיקות הן המועמד הכי טוב להאצלה ל-Claude, במה להסתכל ובמה להתעלם ב-review, ועד כמה החלוקה הזו מחזיקה בפועל.

קודם כל, להניח את המספרים

בדיקות של TopicReview מפוזרות ב-7 קבצים:

spec/services/topic_review_service_spec.rb   760 שורות (88 בדיקות)
spec/requests/topic_reviews_spec.rb          281 שורות (32 בדיקות)
spec/requests/review_appeals_spec.rb         152 שורות (16 בדיקות)
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 בדיקות)
spec/models/topic_review_spec.rb              62 שורות
───────────────────────────────────────────
                                          1,562 שורות

מכסה ארבעה סוגי בדיקות: service (לוגיקה עסקית), request (controller + אינטגרציה), policy (הרשאות Pundit), job (משימות מתוזמנות).

הקומיט הראשון d162f1e נושא Co-Authored-By: Claude Sonnet 4.6 ומנחית 1,100+ מאותן שורות בבת אחת. כל קומיט של 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

סתימת פערים, לא עבודה מחדש. הפרט הזה חוזר בהמשך.

למה בדיקות הן המועמד הכי טוב להאצלה

ארבע סיבות קשיחות:

1. קלט ופלט מפורשים. בדיקה בעצם היא "בהינתן מצב זה → צפה להתנהגות זו". זהו הצד החזק של Claude: תרגום מפרט ל-assertion. קוד עסקי דורש לעיתים שיקולים; בדיקות כמעט אף פעם לא.

2. מכני × נפח גבוה. describe .open! יחיד חייב לכסות "יש חברי מושבעים מתאימים / אין / אין topic / יש כבר review פעיל"—ארבעה context-ים, עם 2–5 בלוקי it בכל אחד. אדם מתחיל לקצר פינות ב-context השלישי. Claude כותב את ה-it ה-88 באותה דקדקנות כמו הראשון.

3. לולאת משוב קצרה במיוחד. כותבים בדיקה, מריצים rspec, תוך שניות יודעים אם עוברת. קוד עסקי דורש ימי שימוש ממשי עד שבעיות צפות. לולאה קצרה = כל טעות של Claude נתפסת על ידי rspec במקום—לא צריך לשמור.

4. מקבילי מטבעו. בלוקי it עצמאיים, ללא צימוד נסתר, מתרחבים בקלות. לייצר עשרות בדיקות מבודדות בבת אחת זה בדיוק הצד החזק של Claude.

במה מסתכלים ב-review ומה מתעלמים ממנו

זה הציר של כל החלוקה.

להתעלם:

  • אם תחביר RSpec נכון → Claude כמעט לא טועה
  • איכות mock → אם אין over-mock ברור, בסדר
  • אסתטיקה של factory → לא חשוב, אם זה רץ זה רץ
  • עקביות סגנון → אם משהו לא במקום, Claude יתקן הכל בפקודה אחת

להסתכל:

  • האם edge cases באמת מכוסים
  • האם שמות הבדיקות מתארים את ההתנהגות הצפויה האמיתית
  • האם חסרות בדיקות שאמורות להתקיים

הנקודה האחרונה היא הערך האמיתי של review. Claude מכסה בדיקות "שעולות בדעתו", אבל אלו שלא עולות לא נכתבות מעצמן. כאן בדיוק נכנס ה-review האנושי—ללכת אחורה מחוקי העסק לכיסוי החסר.

דוגמה קונקרטית: מה 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

עכשיו הסתכל בבדיקות—איזו בדיקה טוענת ש"מחבר הפוסט לעולם לא נבחר כמושבע"?

מחפש ב-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), אף אחד לא מוגן בבדיקה. אם מישהו בעתיד יבצע refactor ל-eligible_jurors ובטעות יוריד את post.user_id מרשימת ההחרגה, כל הבדיקות הקיימות יעברו—ופרודקשן ישקוט ויאפשר למחברים לשבת במושבעים של עצמם.

Claude לא טעה—הוא בדק את מה שהתבקש לבדוק. הוא פשוט לא שאל מיוזמתו: "האם כל אחד משלושת הכללים האלה צריך כיסוי בדיקה?" השאלה הזו—מחוקים לכיסוי לאחור—היא העבודה של review.

(בהגינות: גם אני פספסתי את זה ב-review הראשון. ראיתי זאת רק באודיט שני תוך כדי כתיבת הפוסט הזה. אז גם review אינו חד-פעמי—אבל הוא עדיין פי 10 יותר טוב מלא לבצע review.)

הקומיטים שבאו אחר כך מוכיחים שהחלוקה מחזיקה בפועל

אם "Claude כותב + אדם עושה review" היה מושלם, לא היו קומיטים חדשים של בדיקה אחרי הראשון. המציאות מעניינת יותר—סתימת פערים בלי כתיבה מחדש:

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 הוא התיקון, 00393fc הבדיקה המצורפת. אותו דפוס לשני, בעקבות abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.

שני הקומיטים האלה מוכיחים שני דברים בבת אחת:

  • ה-review לא תפס 100% מהמקרים—ולכן פרודקשן חשף שני באגים
  • אבל ארכיטקטורת הבדיקות החזיקה; יכולנו להמשיך להוסיף בדיקות בלי שינוי מבני—ולכן הקומיטים הם "Add test for..." ולא "Rewrite ... spec"

"מספיק טוב + אפשר להמשיך לטלא" הוא רף ריאלי הרבה יותר מ"מושלם". רדיפה אחרי review מושלם היא בדיוק מה שמונע ממך להעביר בדיקות ל-Claude. קבלת "מספיק טוב" היא מה שמפעיל את החלוקה.

בדיקות שלא צריך להעביר ל-Claude בשלמותן

לא כל בדיקה מתאימה להעברה מלאה:

  • Happy-path E2E—דורשים זווית פרודקטית. Claude יכול לכתוב אבל נוטה לכסות "טכנית ניתן להשלמה" ולפספס "איפה המשתמש באמת נתקע"
  • בדיקות אבטחה—דורשות מחשבת תוקף. Claude שמרני, מפספס משטחי תקיפה לא-סטנדרטיים (הזרקת מילות מפתח SQL, מחרוזות ארוכות מדי, unicode חלופי)
  • בסיסי ביצועים—דורשים מספרים מסביבת deploy אמיתית. Claude יזרוק ספים באקראי
  • שיפוץ גדול של fixture / factory—זה ברמת ארכיטקטורה; חזרה ל-plan mode, זה לא מה ש-review תופס

במקרים כאלה אדם מוביל ו-Claude עוזר.

תצורת ברירת המחדל

לקחת את החלוקה הזו ולהפוך לברירת מחדל שאפשר להריץ:

  1. לפני שהפיצ'ר מתחיל, אני מסביר את חוקי העסק (לא מוסכמות RSpec)
  2. Claude כותב מימוש ובדיקות
  3. מריצים את הבדיקות. עובר = ממשיכים. נופל = Claude מתקן בעצמו
  4. אני עושה review:
    • לא מסתכל על תחביר / mock / factory
    • מסתכל על כיסוי: האם כל חוק עסקי מוגן לפחות בבדיקה אחת?
    • חוקר edge cases: "0 שורות / null / קונקרנסי / הפרת הרשאה"—אחד אחרי השני
    • קורא שמות בדיקות—אם מהשם אי אפשר לנחש מה נבדק, Claude ישנה את השם
  5. באגים שמתגלים בפרודקשן חוזרים כ-regression tests—זה בלאי רגיל של החלוקה, לא כישלון

לסיום

למתכנת יש פחות משאב נפשי לקריאת בדיקות מאשר לקריאת קוד. בדיקות חזרתיות, מכניות, מתישות והכרחיות. כל זה מתאר בדיוק את הצד החזק של Claude—הוא לא משתעמם, לא מתעייף, לא קוטע פינות ב-it ה-50.

העבודה שלך אינה "לכתוב בדיקות"—אלא "לוודא שכל חוק עסקי מכוסה בבדיקה". אחד הוא מימוש והשני שיפוט. השיפוט נשאר איתך; המימוש הולך ל-Claude.

119 ספקים / 1,562 שורות נשלחים בקומיט אחד וחיים מעל שבועיים ללא rework—לא כי אני כותב בדיקות יותר טוב, אלא כי בכלל לא כתבתי. אני רק עושה דבר אחד ש-Claude לא עושה: מחליט אילו חוקי עסק ראויים להגנה.