الاختبار الطفري في Go: كيف تكتشف ما تفوته اختبارات الوحدة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: الاختبارات تنجح، لكن الأخطاء باقية؟

\n\n

تخيل الموقف: لقد كتبت وحدة برمجية بلغة Go، وغطيتها باختبارات الوحدة بنسبة 95% وفقًا لـ go test -cover، وجميع الاختبارات خضراء. أنت واثق من أن الكود يعمل بشكل مثالي. ولكن في بيئة الإنتاج يظهر خطأ. هل هذا مألوف؟

\n\n

المشكلة هي أن مقياس تغطية الكود (code coverage) هو مجرد عدد الأسطر التي تم تنفيذها. إنه لا يخبرك بما تختبره الاختبارات بالضبط. يمكن أن يكون لديك تغطية 100%، ولكنك لم تتحقق أبدًا من أن الدالة ترجع القيمة الصحيحة، وليس فقط أنها لا تتعطل مع خطأ panic.

\n\n

هنا يأتي دور الاختبار الطفري (mutation testing) في Go - وهي منهجية تقوم بإدخال أخطاء بشكل مصطنع في الكود الخاص بك وترى ما إذا كانت الاختبارات قادرة على اكتشافها. إذا لم تفشل الاختبارات على الكود المُطفّر، فهذا يعني أن اختباراتك عديمة الفائدة.

\n\n

في هذه المقالة، سنشرح ما هو الاختبار الطفري في Go، وكيفية إعداده، ولماذا هو أمر لا غنى عنه للمشاريع التي تكون فيها الموثوقية أمرًا بالغ الأهمية. هيا بنا!

\n\n

ما هو الاختبار الطفري؟

\n\n

الاختبار الطفري هو طريقة لتقييم جودة الاختبارات، حيث يتم إجراء تغييرات صغيرة (طفرات) على البرنامج تحاكي الأخطاء النموذجية للمطور. ثم يتم تشغيل الاختبارات. إذا فشل اختبار واحد على الأقل - يتم "قتل" الطفرة، والاختبار يعمل. إذا نجحت جميع الاختبارات - "نجت" الطفرة، وهذه علامة حمراء: لديك ثغرة في الاختبارات.

\n\n

في Go، يُشار إلى الاختبار الطفري غالبًا باسم mutation testing golang، وهو مفيد بشكل خاص للتحقق من منطق الأعمال، والمُحققّات (validators)، والمُحلّلات (parsers)، والمكونات الهامة الأخرى.

\n\n

مثال على طفرة

\n\n

لنفترض أن لديك دالة بسيطة:

\n\n
func IsEven(n int) bool {\n    return n%2 == 0\n}
\n\n

الطفرة النموذجية هي استبدال == بـ !=:

\n\n
func IsEven(n int) bool {\n    return n%2 != 0\n}
\n\n

إذا كان اختبارك يتحقق فقط من أن IsEven(2) لا يتعطل، ولكنه لا يتحقق من أن النتيجة هي true، فستنجو الطفرة. سينجح الاختبار، على الرغم من أن الدالة تعمل الآن بشكل معاكس تمامًا.

\n\n

لماذا لا تكفي تغطية الكود البسيطة؟

\n\n

يعتاد العديد من مطوري Go على الاعتماد على go test -cover. هذا مقياس مفيد، لكنه مضلل. لنلق نظرة على مثال:

\n\n
func Divide(a, b int) (int, error) {\n    if b == 0 {\n        return 0, errors.New("division by zero")\n    }\n    return a / b, nil\n}
\n\n

الاختبار:

\n\n
func TestDivide(t *testing.T) {\n    result, err := Divide(10, 2)\n    if err != nil {\n        t.Fatal(err)\n    }\n    if result != 5 {\n        t.Errorf("expected 5, got %d", result)\n    }\n}
\n\n

التغطية؟ 100% - تم تنفيذ جميع الأسطر. ولكن ماذا لو قام شخص ما عن طريق الخطأ باستبدال a / b بـ a * b؟ لن يلاحظ الاختبار ذلك، لأن 10 * 2 = 20، ونحن توقعنا 5. هل سيفشل الاختبار؟ لا، إذا لم نتحقق من القيمة الدقيقة.

\n\n

سيقوم الاختبار الطفري في Go على الفور بكشف هذه المشكلة، عن طريق إنشاء طفرة مع عملية الضرب والتحقق مما إذا كان الاختبار سيفشل.

\n\n

أدوات الاختبار الطفري في Go

\n\n

الأداة الرئيسية هي go-mutesting. وهي مكتبة شائعة تقوم تلقائيًا بإنشاء الطفرات وتشغيل الاختبارات. التثبيت:

\n\n
go get github.com/zimmski/go-mutesting/cmd/go-mutesting
\n\n

هناك أيضًا بدائل مثل mutesting من شركة "Go Test It"، لكن go-mutesting هو المعيار الفعلي.

\n\n

كيفية التشغيل

\n\n

التشغيل الأساسي:

\n\n
go-mutesting ./...
\n\n

سيجد هذا الأمر جميع ملفات _test.go في المشروع ويتحقق من أي الطفرات تنجو. يتم عرض النتيجة في وحدة التحكم مع تقرير مفصل.

\n\n

مثال عملي: الاختبار الطفري على Go

\n\n

لنقم بإنشاء حزمة بسيطة calculator تحتوي على دوال الجمع و

المدونات

توصيات الكتب