דילוג לתוכן המרכזי
מודול 9 · שיעור 2 מתוך 542/50 בקורס~14 min
שיעורי מודול (2/5)

מבחנים מונחי שולחן

מבחנים מונחי טבלה הם הדפוס האידיומטי של Go: במקום לכתוב N פונקציות TestThisSpecificCase נפרדות, אתה מצהיר על חלק של מקרים וחוזר על עצמו. יש בלוק קביעה אחד, ונתוני הבדיקה מופרדים מהלוגיקה.

תבנית הבסיס

Go
func TestAbs(t *testing.T) {
    cases := []struct {
        name string
        in   int
        want int
    }{
        {"pos", 3, 3},
        {"neg", -3, 3},
        {"zero", 0, 0},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            if got := Abs(tc.in); got != tc.want {
                t.Errorf("Abs(%d) = %d; voglio %d", tc.in, got, tc.want)
            }
        })
    }
}

שלושה מרכיבים:

  1. חתך של מבנה אנונימי עם השדות הרגילים: name, התשומות, הפלט הצפוי (ואולי שגיאה צפויה).
  2. for _, tc := range cases - לולאת הביצוע.
  3. t.Run(tc.name, func(t *testing.T) { ... }) — כל מקרה הופך לתת-מבחן עם השם שלו.

למה t.Run?

t.Run יוצר מבחן משנה הכולל:

  • שם הפרט מוצג בפלט: TestAbs/pos, TestAbs/neg, ...
  • שלו t *testing.T: כשל במקרה אחד לא עוצר את האחרים (אלא אם כן נעשה שימוש ב-t.Fatal).
  • סינון שורת פקודה: go test -run TestAbs/neg פועל רק במקרה זה.
  • מקביליות אופציונלית: התקשר ל-t.Parallel() בתוך ה-func כדי לבצע מקבילה.
Bash
go test -v -run TestAbs/neg

מקרים עם שגיאות צפויות

Go
cases := []struct {
    name    string
    in      string
    want    int
    wantErr bool
}{
    {"ok", "42", 42, false},
    {"bad", "x", 0, true},
}
for _, tc := range cases {
    t.Run(tc.name, func(t *testing.T) {
        got, err := Atoi(tc.in)
        if (err != nil) != tc.wantErr {
            t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
        }
        if !tc.wantErr && got != tc.want {
            t.Errorf("got %d, want %d", got, tc.want)
        }
    })
}

מלכודת סגירת הלולאה (pre-Go 1.22)

בגרסאות שלפני Go 1.22, המשתנה tc שותף על פני איטרציות: קריאה ל-t.Parallel() בתוך t.Run גרמה לכל בדיקות המשנה לפעול נגד אותו מקרה אחרון. התיקון היה:

Go
for _, tc := range cases {
    tc := tc // local rebind (pre-1.22)
    t.Run(tc.name, func(t *testing.T) {
        t.Parallel()
        // ...
    })
}

מתן שמות למקרים

שיטות עבודה מומלצות:

  • שם קצר אך מעורר: "empty input", "too large", "negative".
  • אין רווחים אם אתה רוצה לסנן בקלות עם -run (או להשתמש בקו תחתון).
  • כלול תמיד מקרה של "שביל שמח", מקרה של "קצה" (אפס, ריק) ולפחות מקרה שגיאה אחד.

תרגילים

פעילות גופנית#go.m9.l2.e1
ניסיונות: 0טוען...

הגדר נתח מקרים של מבנה אנונימי עם שדות ב-ורצוי, מאוכלס ב-3 מקרים לבדיקת Abs (חיובי, שלילי, אפס).

טוען עורך...

הפתרון זמין לאחר 3 ניסיונות

פעילות גופנית#go.m9.l2.e2
ניסיונות: 0טוען...

הוסף את t.Run עם tc.name כדי להפוך כל מקרה לתת-מבחן בעל שם.

טוען עורך...

הפתרון זמין לאחר 3 ניסיונות

Quiz#go.m9.l2.e3
מוכן

מה היתרון העיקרי של התבנית מונעת הטבלה על פני N פונקציות מבחן נפרדות?

Go
for _, tc := range cases {
  t.Run(tc.name, func(t *testing.T) { ... })
}
אפשרויות תשובה