اختباراتك تكذب وأنت لا تعلم
\n\nتخيل موقفًا: لقد كتبت اختبارات وحدة، وcode coverage (تغطية الكود) تصل إلى 95%. المدير سعيد، وخط أنابيب CI/CD أخضر. لكن مساء الجمعة يظهر خطأ في الإنتاج. هل هذا مألوف؟
\n\nالمشكلة أن تغطية الكود التقليدية هي وهم. فهي تظهر أي أسطر الكود تم تنفيذها، لكنها لا تجيب على السؤال الرئيسي: هل تختبر الاختبارات المنطق فعلاً؟ هنا يأتي دور mutation testing - وهي منهجية "تكسر" كودك بلا رحمة لتتحقق من جودة اختباراتك.
\n\nفي هذه المقالة، سنشرح ما هو الاختبار الطفري في Java، وكيف يعمل، وسنقدم أمثلة حقيقية باستخدام أداة PIT (Pitest). ستتعلم كيف تحول اختباراتك من "علامة في التقرير" إلى حماية حقيقية من الأخطاء.
\n\nما هو الاختبار الطفري (mutation testing) ولماذا هو مهم؟
\n\nMutation testing (أو الاختبار الطفري) هو طريقة لتقييم جودة الاختبارات، حيث يتم إدخال تغييرات صغيرة ومتعمدة خاطئة في الكود الأصلي (تسمى الطفرات). بعد ذلك، يتم تشغيل اختباراتك. إذا فشل اختبار واحد على الأقل - تم "قتل" المتحول، والاختبار نجح. أما إذا نجحت الاختبارات - فإن المتحول "نجا"، وهذا جرس إنذار.
\n\nتشبيه بسيط: أنت تختبر مدى جودة نظام الإنذار الخاص بك في اكتشاف اللصوص. تضع "لصًا" (متحولًا) وترى ما إذا كان الإنذار (الاختبار) سيعمل. إذا غادر اللص بهدوء - فإن نظام الإنذار لا قيمة له.
\n\nلماذا تغطية الكود (code coverage) مؤشر سيء؟
\n\nتخيل هذا الكود:
\n\npublic boolean isAdult(int age) {\n if (age >= 18) {\n return true;\n }\n return false;\n}\n\nوالاختبار:
\n\n@Test\nvoid testIsAdult() {\n assertTrue(isAdult(18));\n}\n\nستظهر تغطية الكود 100% (هل تم تنفيذ فرعي if-else؟). لا! تم تنفيذ الفرع true فقط. لكن الاختبار ناجح. والآن تخيل أن شخصًا ما غير الشرط بالخطأ إلى age > 21. سيظل الاختبار ناجحًا، لأن 18 > 21 هي false، بينما assertTrue تتوقع true. سينتقل الخطأ إلى الإنتاج.
الاختبار الطفري سيكتشف هذا الموقف فورًا، عن طريق إنشاء متحول يغير >= إلى > أو يعكس الشرط.
كيف يعمل الاختبار الطفري: النظرية ببساطة
\n\nتتكون العملية من ثلاث مراحل:
\n\n- \n
- توليد المتحولين. تقوم الأداة (مثل PIT) بإنشاء نسخ متعددة من كودك مع تغييرات طفيفة: تغيير العوامل، إزالة استدعاءات الدوال، عكس الشروط. \n
- تشغيل الاختبارات. لكل متحول، يتم تشغيل اختبارات الوحدة الخاصة بك. \n
- تحليل النتائج. إذا فشل الاختبار - تم قتل المتحول. إذا نجح - فهو حي. النسبة المئوية للمتحولين المقتولين تسمى mutation score (درجة الطفرات). \n
مؤشر جيد: 80-90% من المتحولين مقتولين. أي شيء أقل من ذلك هو سبب لإعادة كتابة الاختبارات.
\n\nمثال عملي على Java باستخدام PIT
\n\nلنأخذ مثالًا حقيقيًا. لنفترض أن لدينا خدمة لحساب الخصم:
\n\npublic class DiscountService {\n public double calculateDiscount(double amount, boolean isPremium) {\n double discount = 0.0;\n\n if (amount > 100) {\n discount = 0.1; // خصم 10%\n }\n\n if (isPremium) {\n discount += 0.05; // إضافي 5%\n }\n\n // الخصم لا يمكن أن يتجاوز 30%\n if (discount > 0.3) {\n discount = 0.3;\n }\n\n return discount;\n }\n}\n\nلنكتب اختبارًا ساذجًا:
\n\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass DiscountServiceTest {\n\n private final DiscountService service = new