مقدمة: لماذا تغطية الكود بنسبة 100% مجرد وهم؟
\nهل شعرت يوماً بالفخر عندما رأيت تقرير PHPUnit يظهر "Code Coverage: 98%"؟ ثم بعد إطلاق التحديث، يظهر خطأ كارثي في بيئة الإنتاج؟ مألوف، أليس كذلك؟ المشكلة أن التغطية التقليدية (line/statement coverage) تقيس فقط أي سطور من الكود تم تنفيذها. إنها لا تتحقق مما إذا كانت اختباراتك تختبر المنطق الفعلي.
\nتخيل أنك كتبت اختباراً يتحقق من أن دالة ما تُرجع true. والدالة نفسها تُرجع true دائماً، حتى لو تم كسرها. سينجح الاختبار، وستكون التغطية 100%، لكنك لن تكتشف الخطأ. هنا يأتي دور اختبار الطفرات في PHP — تقنية تقلب المفاهيم التقليدية عن الاختبارات.
\nفي هذه المقالة، سنشرح ما هو اختبار الطفرات، وكيف يعمل مع أمثلة حقيقية من كود PHP، وكيفية تطبيقه في مشروعك باستخدام مكتبة Infection.
\n\nما هو اختبار الطفرات؟ (وليس سحراً)
\nاختبار الطفرات (Mutation Testing) هو أسلوب لتقييم جودة الاختبارات، حيث يتم إدخال تغييرات صغيرة ومتعمدة (طفرات) في الكود الأصلي. إذا فشل الاختبار بعد التغيير، فهذا يعني أن الطفرة قُتلت والاختبار يعمل. إذا بقي الاختبار ناجحاً، فهذا يعني أن الطفرة نجت، واختبارك لا يختبر شيئاً.
\nتشبيه بسيط: أنت حارس أمن، واختباراتك هي كاميرات المراقبة. اختبار الطفرات يُدخل "مُخربين" (طفرات) إلى المبنى. إذا رصدت الكاميرا (الاختبار) المُخرب، فهي تعمل. إذا تجول المُخرب بحرية، فالكاميرا عديمة الفائدة.
\nفي PHP، الأداة الرئيسية لذلك هي Infection — مكتبة تُشغل PHPUnit على كل طفرة وتحلل النتائج.
\n\nكيف يبدو ذلك عملياً؟
\nلنفترض أن لديك كلاس آلة حاسبة بسيط:
\n<?php\nclass Calculator\n{\n public function add(int $a, int $b): int\n {\n return $a + $b;\n }\n}\nواختبار له:
\n<?php\nuse PHPUnit\\Framework\\TestCase;\n\nclass CalculatorTest extends TestCase\n{\n public function testAdd()\n {\n $calc = new Calculator();\n $result = $calc->add(2, 3);\n $this->assertEquals(5, $result);\n }\n}\nستُنشئ أداة Infection طفرة، وتستبدل + بـ -:
return $a - $b;\nثم تُشغل الاختبار. إذا فشل الاختبار، قُتلت الطفرة. إذا نجح الاختبار (وسينجح لأنك تختبر فقط 2+3، وليس 2-3) — نجت الطفرة. هذه إشارة: اختبارك ليس جيداً بما يكفي.
\n\nتثبيت Infection والتشغيل الأول
\nأولاً، نثبّت Infection عبر Composer:
\ncomposer require --dev infection/infection\nثم ننشئ ملف الإعدادات infection.json (أو شغّل vendor/bin/infection للإعداد التفاعلي).
مثال على إعدادات بسيطة:
\n{\n "source": {\n "directories": [\n "src"\n ]\n },\n "phpUnit": {\n "configDir": "."\n },\n "mutators": {\n "@default": true\n }\n}\nنُشغل اختبار الطفرات:
\nvendor/bin/infection --min-msi=100\nحيث --min-msi=100 يعني أننا نريد قتل 100% من الطفرات (مثالي، لكن نادراً ما يتحقق). سينشئ Infection تقريراً بصيغة HTML (مجلد infection.html)، حيث سيتم تمييز الطفرات الناجية باللون الأحمر.
أنواع الطفرات في PHP (مع أمثلة)
\nيدعم Infection عشرات المُطفرات. إليك الأكثر شيوعاً:
\n\n1. العوامل الرياضية
\nاستبدال + بـ -، * بـ /، % بـ *، إلخ.
// الكود الأصلي\nreturn $price * $quantity;\n\n// الطفرة\nreturn $price / $quantity;