الانتقال إلى المحتوى الرئيسي
الوحدة 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) { ... }) — تصبح كل حالة اختبارًا فرعيًا باسمها الخاص.

لماذا محمي_INLINE_4؟

يقوم t.Run بإنشاء اختبار فرعي يحتوي على:

  • الاسم الفردي الموضح في الإخراج: TestAbs/pos، TestAbs/neg، ...
  • خاص به t *testing.T: الفشل في إحدى الحالات لا يوقف الحالات الأخرى (ما لم يتم استخدام t.Fatal).
  • تصفية سطر الأوامر: go test -run TestAbs/neg يعمل على تشغيل هذه الحالة فقط.
  • التوازي الاختياري: اتصل بـ t.Parallel() داخل الوظيفة للتوازي.
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)
        }
    })
}

مأزق إغلاق الحلقة (ما قبل الذهاب 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 حالات لاختبار القيمة المطلقة (إيجابية، سلبية، صفر).

جارٍ تحميل المحرر…

الحل متاح بعد 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) { ... })
}
خيارات الإجابة