دروس الوحدة (2/5)
الاختبارات المعتمدة على الجدول
الاختبارات المبنية على الجداول هي النمط الاصطلاحي لـ Go: فبدلاً من كتابة N من الوظائف المنفصلة TestThisSpecificCase، يمكنك الإعلان عن شريحة من الحالات وتكرارها. توجد كتلة تأكيد واحدة، ويتم فصل بيانات الاختبار عن المنطق.
النمط الأساسي
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)
}
})
}
}ثلاثة مكونات:
- شريحة من البنية المجهولة تحتوي على الحقول المعتادة:
name، والمدخلات، والمخرجات المتوقعة (وربما خطأ متوقع). for _, tc := range cases— حلقة التنفيذ.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()داخل الوظيفة للتوازي.
go test -v -run TestAbs/negالحالات مع الأخطاء المتوقعة
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 تسبب في تشغيل جميع الاختبارات الفرعية مقابل نفس الحالة الأخيرة. كان الإصلاح:
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(أو استخدم الشرطة السفلية). - قم دائمًا بتضمين حالة "المسار السعيد"، وحالة "الحافة" (صفر، فارغة) وحالة خطأ واحدة على الأقل.
تمارين
حدد شريحة حالات من بنية مجهولة تحتوي على الحقول الموجودة والمطلوبة، ومليئة بـ 3 حالات لاختبار القيمة المطلقة (إيجابية، سلبية، صفر).
الحل متاح بعد 3 من المحاولات
أضف t.Run باستخدام tc.name لتحويل كل حالة إلى اختبار فرعي مسمى.
الحل متاح بعد 3 من المحاولات
ما هي الميزة الرئيسية للنمط القائم على الجدول مقارنة بوظائف الاختبار المنفصلة N؟
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { ... })
}