0Pricing
PHP Academy · درس

اختبار الطفرات باستخدام Infection

قِس مدى جودة اختباراتك فعليًا

اختبار الطفرات باستخدام Infection درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.

التغطية تكذب

قد تبدو تغطية الأسطر بنسبة 100% مطمئنة، لكنها تثبت فقط أن اختباراتك نفّذت الشيفرة، لا أنها ستتمكن من اكتشاف خطأ فيها. فقد ينفّذ الاختبار سطرًا من الشيفرة من دون أن يتحقق من شيء ذي معنى. يقيس اختبار الطفرات الأمر الحقيقي: هل ستفشل اختباراتك إذا أصبحت الشيفرة معطوبة بطريقة دقيقة؟ تُعد Infection الأداة القياسية في PHP لهذا الغرض.

composer require --dev infection/infection

الفكرة الأساسية: الطفرات

تأخذ Infection الشيفرة التي تغطيها الاختبارات وتُدخل فيها عيوبًا صغيرة — تُسمى طفرات. فتبدّل > إلى >=، و+ إلى -، و&& إلى ||، أو تزيل return، وغير ذلك. ثم تعيد تشغيل اختباراتك على كل طفرة:

  • إذا فشل اختبار → فقد قُضي على الطفرة، وهذا جيد لأن اختباراتك اكتشفت العيب.
  • إذا نجحت جميع الاختبارات → فقد نجت الطفرة، وهذا سيئ لأن عيبًا حقيقيًا في هذا الموضع سيمر دون اكتشاف.

طفرة ناجية أثناء العمل

لنتأمل هذه الدالة واختبارًا ضعيفًا لها. ستبدّل Infection الرمز >= إلى >. إذا لم يتحقق أي اختبار من الحد الفاصل الدقيق، أي عندما يساوي المبلغ العتبة، فستنجو الطفرة، لتكشف عن حالة طرفية لم يختبرها أحد.

<?php
function qualifiesForFreeShipping(float $total): bool
{
    return $total >= 50.0;   // Infection mutates >= to >
}

// Weak test only checks 100 and 10 -> never tests exactly 50.0
var_dump(qualifiesForFreeShipping(100.0)); // true
var_dump(qualifiesForFreeShipping(10.0));  // false
var_dump(qualifiesForFreeShipping(50.0));  // true  <-- the boundary the mutant exposes

القضاء على الطفرة

أضف التأكيد الخاص بالحد الفاصل، فتموت طفرة التحويل من >= إلى >: في الشيفرة الطافرة تكون 50.0 > 50.0 مساوية لـ false، ولذلك يفشل الاختبار — وهذا ما نريده تمامًا. يخبرك اختبار الطفرات حرفيًا بالتأكيدات المفقودة.

<?php
use PHPUnit\Framework\TestCase;

final class ShippingTest extends TestCase
{
    public function test_threshold_is_inclusive(): void
    {
        // Kills the >= -> > mutant
        self::assertTrue(qualifiesForFreeShipping(50.0));
    }
}

الإعداد: infection.json

تُدار Infection من خلال infection.json5. تحدد فيه المجلدات التي ستُطبّق عليها الطفرات، ومواضع السجلات، والحدود الدنيا للدرجات اللازمة لنجاح CI. وجّه source.directories إلى شيفرة الإنتاج فقط — ولا توجّهه أبدًا إلى اختباراتك.

{
  "source": {
    "directories": ["src"]
  },
  "logs": {
    "text": "build/infection.log",
    "html": "build/infection.html"
  },
  "mutators": {
    "@default": true
  },
  "minMsi": 80,
  "minCoveredMsi": 90
}

مقاييس MSI

تُصدر Infection مؤشر Mutation Score Indicator:

  • MSI = عدد الطفرات التي قُضي عليها / إجمالي عدد الطفرات. وتتأثر سلبًا بالشيفرة غير المغطاة، لأن تلك الطفرات لا يمكن القضاء عليها.
  • Covered MSI = عدد الطفرات التي قُضي عليها / عدد الطفرات الموجودة في الأسطر المغطاة. ويقيس جودة تأكيداتك في المواضع التي تختبرها فعلًا.
  • Mutation Code Coverage = مقدار الشيفرة التي تمكنت Infection من تطبيق الطفرات عليها أصلًا.

تُعد التغطية العالية للأسطر مع انخفاض Covered MSI الإشارة التقليدية إلى اختبارات تفتقر إلى التأكيدات الكافية.

تشغيل Infection بكفاءة

اختبار الطفرات مكلف، إذ يعيد تشغيل مجموعة الاختبارات مرة لكل طفرة. هناك طريقتان رئيسيتان لتسريعه: شغّل الاختبارات بالتوازي باستخدام --threads، وطبّق الطفرات على الشيفرة التي غيّعها فرعك الحالي فقط باستخدام تصفية Git diff، وهو أمر مثالي لـ CI عند إنشاء طلبات السحب.

vendor/bin/infection --threads=max --git-diff-lines --git-diff-base=origin/main

قراءة الطفرات الناجية

تكمن القيمة في الفروقات التي تطبعها Infection لكل طفرة ناجية. فهي تعرض السطر الدقيق والتغيير الذي فشلت اختباراتك في اكتشافه. تعامل مع الطفرات الناجية كقائمة مهام: أضف التأكيد المفقود، أو أقرّ بأن الطفرة غير مؤذية، أي أنها طفرة مكافئة.

- return $total >= 50.0;
+ return $total > 50.0;

# Mutant survived: no test asserts the inclusive boundary (total === 50.0)

الطفرات المكافئة والتجاهل

بعض الطفرات مكافئة؛ فهي تغيّر الشيفرة، لكنها لا تغيّر سلوكها القابل للملاحظة، ولذلك لا يمكن لأي اختبار القضاء عليها. لا يمكن القضاء على هذه الطفرات، وهي تخفض MSI بشكل غير عادل. عطّل مولدات الطفرات المعروفة بإيجابيتها الكاذبة في شيفرة محددة بدلًا من تعديل الاختبارات بطريقة مصطنعة لملاحقتها.

{
  "mutators": {
    "@default": true,
    "Plus": {
      "ignore": ["App\\Math\\Statistics::variance"]
    }
  }
}

أين يحقق اختبار الطفرات أكبر فائدة

لأنه بطيء، طبّقه في المواضع المناسبة:

  • طبّقه على منطق المجال الأساسي، مثل التسعير والصلاحيات والحسابات، حيث تكون الأخطاء الصامتة مكلفة.
  • اجعل اجتياز طلبات السحب مشروطًا بـ Covered MSI للأسطر التي تغيّرت، لا بالمستودع بأكمله.
  • لا تطارد نسبة 100% على مستوى المشروع كله؛ فالعوائد المتناقصة والطفرات المكافئة تجعل ذلك هدرًا.
  • استخدمه للعثور على التأكيدات الضعيفة، ثم أصلح الاختبارات؛ فالدرجة وسيلة وليست هدفًا.

يحتاج إلى بيانات التغطية ليكون سريعًا

تطبّق Infection الطفرات على الأسطر التي تغطيها اختباراتك فعلًا فقط، ولذلك تعيد استخدام بيانات التغطية التي ينتجها مشغّل الاختبارات. يكون ذلك بطيئًا مع Xdebug؛ أما pcov فأسرع بكثير في تغطية الأسطر، وهو المشغّل الموصى به لتشغيل اختبارات الطفرات. ويمكن لـ Infection أيضًا إنشاء بيانات التغطية بنفسها أو استهلاك تقرير تغطية أنشأته مسبقًا في CI.

# Faster mutation runs: use pcov instead of Xdebug for coverage
php -d pcov.enabled=1 vendor/bin/infection --threads=max

# Or reuse coverage already generated by your PHPUnit step:
vendor/bin/infection --coverage=build/coverage --skip-initial-tests

فحص سريع

ماذا تخبرك الطفرة الناجية؟

مراجعة

تعلّمت قياس جودة الاختبارات، لا كميتها فحسب:

  • يُدخل اختبار الطفرات عيوبًا صغيرة، أو طفرات؛ فالقضاء على الطفرة يعني اكتشاف العيب، ونجاتها تعني وجود فجوة.
  • توضح التغطية ما نُفّذ، بينما يوضح Covered MSI قوة التأكيدات.
  • اضبط الأداة عبر infection.json5 باستخدام حدود MSI؛ وشغّلها مع --threads وتصفية Git diff لتسريعها.
  • الطفرات الناجية قائمة مهام؛ وانتبه إلى الطفرات المكافئة وتجاهلها عن قصد.
  • استهدف المنطق الأساسي، واجعل طلبات السحب مشروطة بـ MSI للأسطر التي تغيّرت، بدلًا من مطاردة نسبة 100% على مستوى المشروع كله.

الأسئلة الشائعة

هل درس «اختبار الطفرات باستخدام Infection» مجاني؟

نعم — نص درس «اختبار الطفرات باستخدام Infection» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.

ماذا ستتعلم في «اختبار الطفرات باستخدام Infection»؟

قِس مدى جودة اختباراتك فعليًا تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟

لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «اختبار الطفرات باستخدام Infection»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟

نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. سير عمل التطوير المدفوع بالاختبارات
  2. المحاكاة والبدائل باستخدام Mockery
  3. اختبار التكامل والاختبار الوظيفي
  4. اختبار الطفرات باستخدام Infection
← العودة إلى PHP Academy