يا جماعة الخير، السلام عليكم ورحمة الله وبركاته. معكم أخوكم أبو عمر.
خلوني أحكيلكم قصة صارت معي قبل كم سنة، قصة علمتني درس قاسي عن الغرور والثقة الزائدة في عالم البرمجة. كنا شغالين على نظام حساس جداً لأحد العملاء، نظام بيتعامل مع عمليات مالية معقدة. أنا وفريقي قضينا شهور طويلة في كتابة الكود وبناء الاختبارات الآلية (Unit Tests). كنا مهووسين بالجودة، وكان هدفنا الأسمى هو الوصول إلى نسبة تغطية كود (Code Coverage) تبلغ 100%.
بعد ليالٍ طويلة من العمل، وشرب ما لا يحصى من فناجين القهوة، أتى ذلك اليوم المشهود. أظهرت لوحة التحكم الخاصة بالاختبارات الرقم السحري: 100% Code Coverage! يا الله، شعور لا يوصف. شعرنا أننا بنينا قلعة حصينة لا يمكن اختراقها. احتفلنا في المكتب، ووزعنا الكنافة، وكنا على ثقة تامة بأن الكود الخاص بنا ” bulletproof” أو “ضد الرصاص” كما يقولون.
بكل ثقة، قمنا بنشر التحديث الجديد على البيئة الإنتاجية (Production). مرت الساعات الأولى بسلام… ثم بدأت الكارثة. بدأت تصلنا تقارير عن أخطاء غريبة في حسابات معينة، أرقام لا منطق فيها. سارعنا لفحص السجلات، وكانت الصدمة: الخطأ كان في دالة (function) بسيطة، دالة كنا متأكدين 100% أنها مغطاة بالاختبارات!
كيف يعقل هذا؟ فتحت الكود، وفتحت ملف الاختبار المقابل لها. نعم، الاختبار كان يمرر قيماً للدالة وينفذ كل سطر فيها، وبالتالي حقق “التغطية”. لكن المصيبة أن الاختبار لم يكن يتحقق من صحة القيمة المرتجعة! كان مجرد اختبار “صوري” يتأكد أن الدالة تعمل دون أن تنهار. كان اختباراً غبياً، ونحن كنا أغبى منه لأننا وثقنا في رقم “100%” الأعمى.
في تلك اللحظة، شعرت أن كل مفهومي عن جودة الاختبارات قد انهار. هذه الثقة الزائفة كادت أن تكلفنا سمعتنا ومصداقيتنا. ومن هنا، بدأت رحلتي في البحث عن طريقة أفضل وأعمق لقياس جودة الاختبارات، لا كميتها. وكانت الإجابة التي أنقذتني هي: الاختبار الطفري (Mutation Testing).
الوهم الكبير: خرافة تغطية الكود 100%
قبل أن نغوص في عالم الاختبار الطفري، دعونا نفكك هذا الوهم الذي وقعت فيه أنا، ويقع فيه الكثيرون. ما هي تغطية الكود، ولماذا هي ليست المقياس النهائي للجودة؟
ما هي تغطية الكود (Code Coverage)؟
ببساطة، تغطية الكود هي مقياس نسبة مئوية يوضح لك أي أجزاء من الكود المصدري الخاص بك تم “تنفيذها” أو “المرور عليها” أثناء تشغيل مجموعة الاختبارات الآلية. لو عندك دالة من 10 أسطر، واختبارك جعل البرنامج ينفذ 8 أسطر منها، فستكون تغطية الأسطر (Line Coverage) هي 80%.
هناك أنواع مختلفة من التغطية، أشهرها:
- تغطية الأسطر (Line Coverage): هل تم تنفيذ كل سطر؟
- تغطية الدوال (Function Coverage): هل تم استدعاء كل دالة؟
- تغطية الفروع (Branch Coverage): هل تم اختبار كل مسارات الشروط (if/else, switch)؟ (مثلاً، هل اختبرت حالة الـ
ifوحالة الـelse؟)
هذه المقاييس مفيدة لتعرف الأجزاء “الميتة” في الكود التي لم يلمسها أي اختبار، لكنها لا تخبرك شيئاً عن جودة الاختبارات نفسها.
لماذا تغطية 100% ليست كافية؟ (الفخ الذي وقعت فيه)
المشكلة تكمن في أن التغطية تخبرك أن سطراً ما قد تم تنفيذه، لكنها لا تعرف لماذا تم تنفيذه أو ماذا تحقق اختبارك منه. دعونا نرى مثالاً بسيطاً بلغة جافاسكريبت يوضح الفكرة.
لنفترض أن لدينا هذه الدالة التي من المفترض أن تقوم بعمل خصم على سعر منتج. ولكن، بالخطأ، المبرمج استخدم عملية الجمع + بدلاً من الطرح -.
// src/calculator.js
function applyDiscount(price, discount) {
if (price <= 0) {
return 0;
}
// خطأ منطقي: يجب أن تكون العملية طرحاً!
return price + discount;
}
الآن، لنكتب اختباراً “ضعيفاً” يحقق تغطية 100% لهذه الدالة:
// tests/calculator.test.js
test('applyDiscount should run without errors', () => {
const result = applyDiscount(100, 10);
// هذا الاختبار سيحقق تغطية 100%
// لكنه لا يتحقق من صحة النتيجة!
expect(typeof result).toBe('number');
});
إذا قمت بتشغيل أداة قياس التغطية (مثل Jest’s coverage reporter)، ستحصل على نتيجة 100% لهذه الدالة. ستحتفل وتشعر بالأمان. لكن في الواقع، اختبارك عديم الفائدة تماماً! هو فقط يتأكد من أن الناتج هو “رقم”، لكنه لا يكتشف أن الدالة تقوم بزيادة السعر (100 + 10 = 110) بدلاً من خصمه (100 – 10 = 90). هذا بالضبط ما حدث معنا، ولكن على نطاق أعقد وأخطر.
دخول البطل: ما هو الاختبار الطفري (Mutation Testing)؟
هنا يأتي دور الاختبار الطفري ليحل هذه المشكلة من جذورها. فكرته عبقرية في بساطتها، وهو يغير المعادلة تماماً: بدلاً من اختبار الكود، نقوم باختبار الاختبارات نفسها!
الفكرة ببساطة: “شو بصير لو غيرنا الكود؟”
تخيل أن هناك “عفريتاً” صغيراً وشقياً (نسميه “الطفرة” أو Mutant) يتسلل إلى الكود المصدري الخاص بك ويقوم بإجراء تغييرات طفيفة جداً فيه. هذا العفريت لا يكسر الكود تماماً، بل يغير المنطق بشكل خبيث. على سبيل المثال:
- يغير
a + bإلىa - b. - يغير
x > yإلىx >= yأوx < y. - يغير
if (condition)إلىif (true)(لحذف الشرط). - يحذف محتوى دالة بالكامل.
بعد أن يقوم العفريت بهذا التغيير (يخلق “نسخة طافرة” من الكود)، يقوم نظام الاختبار الطفري بإعادة تشغيل كل اختباراتك الآلية على هذه النسخة المعدلة. هنا يحدث أحد أمرين:
- أحد اختباراتك يفشل: ممتاز! هذا يعني أن اختباراتك قوية بما يكفي لاكتشاف هذا التغيير الطفيف. نقول هنا أن “الطفرة قد قُتلت” (The mutant is killed). هذا هو المطلوب. ✅
- كل اختباراتك تنجح: كارثة! هذا يعني أن تغيير المنطق في الكود لم يؤثر على أي من اختباراتك. هذا يكشف أن اختباراتك ضعيفة ولا تتحقق من هذا الجزء من المنطق بشكل صحيح. نقول هنا أن “الطفرة قد نجت” (The mutant survived). ⛔️
الهدف: قتل كل “الطفرات” (Mutants)
الهدف من عملية الاختبار الطفري هو “قتل” أكبر عدد ممكن من الطفرات. النتيجة النهائية ليست نسبة تغطية، بل “معدل الطفرات” (Mutation Score)، وهو نسبة الطفرات المقتولة إلى إجمالي الطفرات.
معدل الطفرات = (عدد الطفرات المقتولة / إجمالي عدد الطفرات) * 100
إذا كان معدل الطفرات لديك 90%، فهذا مؤشر قوي جداً على أن اختباراتك ليست مجرد غطاء، بل هي حارس حقيقي لجودة الكود الخاص بك.
كيف نبدأ مع الاختبار الطفري؟ (دليل عملي)
الكلام النظري جميل، لكن “كيف بنطبق هالحكي يا أبو عمر؟”. لنأخذ مثالاً عملياً باستخدام إحدى أشهر الأدوات في عالم جافاسكريبت وتايبسكربت.
أشهر الأدوات في عالم جافاسكريبت: Stryker
Stryker هي أداة رائعة ومفتوحة المصدر تقوم بكل العمل الشاق من أجلك. هي التي تخلق الطفرات، وتشغل الاختبارات، وتعطيك تقريراً مفصلاً بالطفرات التي نجت والتي تحتاج إلى تحسين اختباراتها.
لنفترض أن لديك مشروع جافاسكريبت يستخدم Jest لإجراء الاختبارات. إليك كيفية إضافة Stryker:
- التثبيت:
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner - الإعداد الأولي:
يمكنك تشغيل أمر الإعداد ليقوم بإنشاء ملف الإعدادات لك تلقائياً.
npx stryker initسيقوم هذا الأمر بسؤالك عدة أسئلة عن مشروعك (لغة البرمجة، إطار الاختبارات، إلخ) ثم ينشئ لك ملف
stryker.conf.json(أو .js). - التشغيل:
ببساطة، قم بتشغيل الأمر التالي:
npx stryker run
مثال عملي: لنُصلح اختبارنا الضعيف
لنتذكر دالة الخصم الخاطئة واختبارها الضعيف:
// src/calculator.js
function applyDiscount(price, discount) {
if (price {
const result = applyDiscount(100, 10);
// تأكيد ضعيف
expect(typeof result).toBe('number');
});
عند تشغيل npx stryker run، سيقوم Stryker بتجربة عدة طفرات. إحدى أهم الطفرات التي سيجربها هي تغيير + إلى - في السطر return price + discount;.
ماذا سيحدث؟
- النسخة الطافرة من الكود ستصبح:
return price - discount;. - سيقوم Stryker بتشغيل اختبارنا الضعيف على هذه النسخة.
- الاختبار سيقوم بحساب
100 - 10 = 90. - التحقق
expect(typeof 90).toBe('number')سينجح!
النتيجة؟ سيخبرك Stryker أن هناك “طفرة نجت” (Mutant Survived). سيظهر لك تقرير شبيه بهذا:
----------------|---------------|------------------|
File | % score | # killed / total |
----------------|---------------|------------------|
All files | 50.00 | 1 / 2 |
calculator.js | 50.00 | 1 / 2 |
----------------|---------------|------------------|
# Mutations
[Survived] BinaryOperator: Replaced "+" with "-" in calculator.js:6:10
هذا التقرير هو الذهب! إنه يخبرك بوضوح: “يا صديقي، اختبارك لم يلاحظ عندما غيرت عملية الجمع إلى طرح. اذهب وأصلح اختبارك!”.
كتابة اختبار أفضل لقتل “الطفرة”
الآن، مسلحين بهذه المعلومة، يمكننا كتابة اختبار حقيقي وفعال:
// tests/calculator.test.js
// الاختبار الضعيف السابق
test('applyDiscount should run without errors', () => {
const result = applyDiscount(100, 10);
expect(typeof result).toBe('number');
});
// الاختبار القوي الجديد
test('applyDiscount should correctly subtract the discount from the price', () => {
// نحن نتوقع نتيجة خاطئة بناءً على الكود الخاطئ الحالي
// هذا الاختبار سيفشل مباشرة ويكشف الخطأ في الكود
// ولكن لأغراض الاختبار الطفري، لنفترض أن الكود صحيح (price - discount)
// ونريد التأكد أن اختبارنا قوي
expect(applyDiscount(100, 10)).toBe(90);
});
الآن، لنفترض أننا أصلحنا الكود الأصلي ليصبح return price - discount;. إذا قمنا بتشغيل Stryker مرة أخرى:
- سيقوم Stryker بإنشاء طفرة جديدة، ويغير
-إلى+. - النسخة الطافرة ستصبح:
return price + discount;. - سيشغل Stryker اختبارنا الجديد:
expect(applyDiscount(100, 10)).toBe(90); - النسخة الطافرة ستُرجع
110. - التحقق
expect(110).toBe(90)سيفشل!
النتيجة؟ “تم قتل الطفرة بنجاح!”. سيرتفع معدل الطفرات (Mutation Score)، ومعه سترتفع ثقتك الحقيقية في جودة اختباراتك.
نصائح أبو عمر الذهبية للاختبار الطفري
بعد سنوات من استخدام هذه التقنية، تعلمت بعض الدروس التي أود مشاركتها معكم.
لا تستهدف 100%… مرة أخرى!
تماماً كما هو الحال مع تغطية الكود، لا تجعل هدفك هو الوصول إلى معدل طفرات 100%. هذا غير عملي ومكلف جداً. بعض الطفرات تكون “مكافئة” (Equivalent Mutants)، أي أنها تغير الكود دون أن تغير سلوكه (مثلاً تغيير i++ إلى ++i في سياق معين). تجاهل هذه الحالات وركز على الطفرات التي تكشف ضعفاً حقيقياً في منطق اختباراتك. معدل 80-90% هو هدف رائع وطموح.
اجعله جزءاً من الـ CI/CD Pipeline
الاختبار الطفري أبطأ بكثير من الاختبارات العادية لأنه يعيد تشغيل اختباراتك مئات أو آلاف المرات. لذلك، لا تقم بتشغيله مع كل commit على جهازك المحلي. أفضل ممارسة هي دمجه في مسار التكامل والنشر المستمر (CI/CD) ليعمل على طلبات السحب (Pull Requests) الموجهة للفرع الرئيسي، أو كجزء من عملية بناء ليلية (Nightly Build).
ابدأ صغيراً
إذا كان لديك مشروع ضخم وقديم (legacy)، لا تحاول تشغيل الاختبار الطفري على كامل المشروع من أول يوم، فقد تصاب بالإحباط من بطء العملية والنتائج. ابدأ بتطبيقه على الوحدات (Modules) الجديدة أو الأجزاء الأكثر حساسية في نظامك. شيئاً فشيئاً، يمكنك توسيع النطاق.
حلل التقارير بذكاء
يوفر Stryker تقارير HTML تفاعلية ورائعة. لا تنظر فقط إلى الرقم النهائي. افتح التقرير، وانظر إلى كل طفرة “نجت”. انقر عليها لترى التغيير الذي حدث في الكود، وأي الاختبارات مرت عليها دون أن تكتشفها. هذا التقرير هو أفضل أداة تعليمية لك ولفريقك لتحسين مهاراتكم في كتابة الاختبارات.
الخلاصة: من التغطية العمياء إلى الثقة الحقيقية 💎
رحلتي مع جودة البرمجيات علمتني أن المقاييس يمكن أن تخدعنا. تغطية الكود 100% أعطتني شعوراً زائفاً بالأمان كلفني الكثير من التوتر وكاد أن يكلفني سمعتي. الاختبار الطفري، من ناحية أخرى، كان كالصديق الصريح الذي لا يجاملك، ويخبرك بالحقيقة المرة عن نقاط ضعف اختباراتك.
الانتقال من التركيز على “تغطية الكود” إلى التركيز على “معدل قتل الطفرات” هو انتقال من التفكير في الكمية إلى التفكير في الجودة. هو انتقال من مجرد “تنفيذ” الكود إلى “التحقق” من منطقه بشكل صارم.
نصيحتي الأخيرة لك: لا تثق بالرقم الأعمى، بل ثق بالكود الذي يصمد أمام محاولات التخريب المتعمدة. خلي اختباراتك مش بس تغطي الكود، خليها تحميه وتصونه! ابدأ اليوم، ولو بخطوة صغيرة، وسترى كيف ستتغير نظرتك لجودة الاختبارات إلى الأبد. 💪