تخطَّ إلى المحتوى

اختبار الطفرات بشرح مبسّط

Spec: ISO/IEC/IEEE 29119-4 Spec: PHPUnit Evidence: Test-backed

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

التغطية من أكثر مقاييس الاختبار موثوقيةً، ومن أكثرها تضليلًا في الوقت نفسه. فالاختبار الذي يستدعي دالةً ولا يؤكّد أي شيء ينفّذ كل سطر فيها: تغطية كاملة، وكشف معدوم. تنصّ أدبيات المعايير صراحةً على أن ترتيب معايير التغطية لا يدل على قدرتها على كشف العيوب. هذه القدرة هي الخاصية التي تسمّيها فعالية الاختبار (⁨ISO/IEC/IEEE 29119-4⁩، §⁨C.2.4⁩). نسبة التغطية وضمان العثور على العيوب ادّعاءان مختلفان.

بالنسبة إلى محرّك ⁨PDF⁩، فهذا ليس أمرًا نظريًا. ففحص نطاق بايتات التوقيع، أو إزاحة المرجع المتقاطع، أو فرع ترميز — يمكن للاختبارات أن “تغطّي” كل ذلك تمامًا دون أن تؤكّد أبدًا القيمة المهمة. مجموعة اختبارات خضراء فوق اختبارات ضعيفة أسوأ من فجوة صريحة، لأنها تثبّط فعليًا أي شخص عن البحث.

  • اختبار الطفرات يُجري آلاف التعديلات الصغيرة المتعمّدة (الطفرات) على الشيفرة — قلب < إلى <=، و + إلى -، وقيمة return — ثم يعيد تشغيل الاختبارات على كل طفرة.
  • إذا فشل اختبار بسبب طفرة، فإن الطفرة مقتولة: هناك اختبار أكّد ذلك السلوك فعليًا. وإذا ظلّت جميع الاختبارات ناجحة، فإن الطفرة هربت: نُفِّذ السلوك لكنه لم يُفحص قط.
  • مؤشر درجة الطفرات (⁨MSI⁩) هو، تقريبًا، نسبة الطفرات المقتولة إلى إجمالي الطفرات غير المتكافئة. وهو يقيس ما إذا كانت اختباراتك تكشف التغييرات، لا ما إذا كانت تنفّذ الشيفرة.
  • بعض الطفرات متكافئة — لا يمكنها تغيير السلوك القابل للملاحظة، لذا لا يستطيع أي اختبار قتلها. احتساب هذه إخفاقات أمر غير نزيه. يثبت ⁨NextPDF⁩ هذه الطفرات ويسجّلها في سجلّ بدلًا من استبعادها بصورة غير رسمية.
  • يستخدم ⁨NextPDF⁩ مؤشر ⁨MSI⁩ للعثور على الاختبارات الضعيفة وتقويتها. إنه بوابة تشخيصية في التكامل المستمر، لا رقمًا تسويقيًا.

تُشغَّل الطفرات على المحرّك باستخدام مُطفِّر ⁨Infection⁩. وهو مُهيّأ على شجرة المصدر الإنتاجية، مع تفعيل عائلات المُطفّرات الحسابية، والمنطقية، وحدود الشروط، والمساواة، وقيمة الإرجاع، والإزالة — وهي بالضبط العوامل التي تكشف المنطق “المنفَّذ لكن غير المؤكَّد”. والتدفّق آلي:

  1. Start green The suite must pass before mutation begins.
  2. Mutate Apply one small, deliberate change to the source.
  3. Re-run Run the tests that cover the mutated line.
  4. Killed A test failed — the behaviour is genuinely asserted.
  5. Escaped All tests still pass — a weak spot to strengthen.
  6. Equivalent No test can kill it because behaviour is unchanged — proven and ledgered, not scored as a miss.
حلقة اختبار الطفرات التي يشغّلها NextPDF: ابدأ من اختبارات خضراء، وولّد طفرة، وأعد تشغيل الاختبارات التي تغطّيها، وصنّف الطفرة على أنها مقتولة (التقطها اختبار)، أو هاربة (فجوة تغطية-دون-تأكيد يجب إصلاحها)، أو مُثبَتة التكافؤ (لا يمكن لأي اختبار قتلها؛ تُسجَّل في السجلّ ولا تُحتسب ضد الدرجة).

خياران تصميميان يجعلان الرقم جديرًا بالثقة. أولًا، الدرجة موصولة بوصفها بوابة. يفرض التكامل المستمر حدًا أدنى لمؤشر ⁨MSI⁩ (وحدًا أدنى لمؤشر ⁨MSI⁩ المُغطّى) ويشغّل صيغةً محصورة بالفروق على الأسطر المتغيّرة. ونتيجةً لذلك، فإن أي تغيير يضيف شيفرة دون تأكيدات حقيقية يُلتقط عند المراجعة، لا بعد ذلك. ثانيًا، لا يستبعد ⁨NextPDF⁩ الطفرات المزعجة بصمت. أما الطفرات المتكافئة دلاليًا بحق — مثل !== مقابل != عندما يضمن التنميط الصارم أن كلا المعاملين من النوع نفسه — فتُسجَّل في سجلّ طفرات مع اختبار صريح لإثبات التكافؤ. ونتيجةً لذلك، يعكس عدد الطفرات الهاربة فجوات حقيقية، لا مجرّد ضبط حسابات. ⁨PHPStan⁩ المستوى 10 إضافةً إلى strict_types إضافةً إلى الخصائص المنمَّطة هو ما يجعل تلك الإثباتات على التكافؤ سليمة.

Evidence: Test-backed اختبار الطفرات مُهيّأ في المحرّك على أدلة المصدر الإنتاجية مع تفعيل عائلات المُطفّرات الكاشفة للسلوك. وهو مفروض بوصفه بوابة تكامل مستمر بحدّ أدنى لمؤشر ⁨MSI⁩ وصيغة محصورة بالفروق. إنه فحص بناء، لا أمرًا ثانويًا يُؤجَّل.

Evidence: Test-backed تُعالَج مشكلة الطفرات المتكافئة بنزاهة. تُصنَّف الطفرات المتكافئة دلاليًا وتُدعَم باختبارات مخصّصة لإثبات التكافؤ في سجلّ طفرات، مع ارتكاز سلامة كل إثبات على ⁨PHPStan⁩ المستوى 10 إضافةً إلى التنميط الصارم. ومن ثَمّ يمثّل عدد الطفرات الهاربة سلوكًا حقيقيًا غير مكتشَف، لا ضوضاء غير قابلة للقتل مُضخَّمة لتبدو الدرجة أسوأ مما هي عليه.

Evidence: Standard-backed الطفرات تقنية معترَف بها، لا من ابتكار NextPDF. Spec: ISO/IEC/IEEE 29119-4, §B.2.4 يصف تطبيق طفرات عامة على عناصر مواصفة لاشتقاق طفرات محدّدة للاختبار. والتقنية لازمة أصلًا لأن المعيار نفسه ينصّ على أن ترتيب الاحتواء بين معايير التغطية لا يرتّبها بحسب القدرة على كشف العيوب (⁨ISO/IEC/IEEE 29119-4⁩، §⁨C.2.4⁩).

Evidence: Standard-backed التغطية نفسها معرّفة جيدًا ومحدودة. Spec: PHPUnit يميّز بين تغطية الأسطر والفروع والمسارات في التغطية. فتغطية الأسطر لا تسجّل سوى أن سطرًا قابلًا للتنفيذ قد نُفِّذ. ومعرفة التعريف هي ما يجعل عدم كفايتها بديهيًا.

المهمّ ليس الأمر نفسه — بل ما تكشفه لك الطفرة الهاربة:

<?php
declare(strict_types=1);
final class ByteRange
{
// Suppose the production guard is:
// if ($offset < 0) { throw new InvalidByteRange(); }
public function assertNonNegative(int $offset): void
{
if ($offset < 0) {
throw new InvalidByteRange('offset must be >= 0');
}
}
}
// A test that EXECUTES this line but does not assert the boundary:
// $byteRange->assertNonNegative(5); // no exception expected, none asserted
// gives 100% line coverage of assertNonNegative().
//
// Mutation flips `< 0` to `<= 0`. Behaviour now differs ONLY at $offset === 0.
// If no test passes 0 and asserts what happens, every test still passes:
// the mutant ESCAPED. Coverage said "tested"; mutation said "the boundary
// is unasserted". The fix is a test that pins offset === 0, not a higher
// target.
//
// composer mutation:diff → mutate only changed lines, enforce min MSI
// composer mutation:full → full-tree mutation gate

تلك الطفرة الهاربة هي القيمة المضافة بأكملها. لقد حدّدت تأكيدًا مفقودًا حقيقيًا ومحدّدًا كان تقرير التغطية قد عدّه مُختبَرًا بالكامل.

المفهوم الخاطئ الأبرز هو أن درجة الطفرات علامة يجب تعظيمها. مؤشر ⁨MSI⁩ مرتفع جدًا يُحقَّق بكتابة اختبارات لقتل الطفرات أجوف تمامًا مثل تغطية مرتفعة تُحقَّق باستدعاء الدوال دون تأكيد. فقد جرى التلاعب بالمقياس، ولم يعد يقيس الكشف. يستخدم ⁨NextPDF⁩ مؤشر ⁨MSI⁩ لـالعثور على الاختبارات الضعيفة. والمُخرَج هو تأكيد أفضل؛ والتباهي ليس الغرض صراحةً.

المفهوم الخاطئ الثاني هو أن كل طفرة ناجية عيب في الاختبارات. بعض الطفرات متكافئة بحق ولا يمكن قتلها بأي اختبار، لأنها لا تغيّر السلوك القابل للملاحظة. ومعاملة هذه إخفاقات تنتج درجة منخفضة بصورة مصطنعة وغير نزيهة، وتدرّب الناس على تجاهل التقرير. نهج ⁨NextPDF⁩ هو إثبات التكافؤ صراحةً وتسجيله في السجلّ، لا كبته بصمت أو التظاهر بأن الرقم أسوأ مما هو عليه.

يقيس اختبار الطفرات ما إذا كانت الاختبارات تكشف التغييرات المحقونة. وهو لا يثبت أن الشيفرة صحيحة. وهو لا يقيس الأداء أو المطابقة. ولا يمكنه قتل طفرة متكافئة حقًا. إن درجة الطفرات الحالية، والحدّ الأدنى لمؤشر ⁨MSI⁩ المعمول به، وعدد المتكافئات المسجَّلة، وأي رقم تغطية هي إشارات جودة حيّة تُولَّد من مُخرجات التكامل المستمر وتُنشر مع البناء. وهي غائبة هنا عمدًا، لأن أي رقم يُلصَق في نصّ يصبح قديمًا ويتحوّل إلى كذبة صغيرة. الحقيقة الثابتة الوحيدة التي تذكرها هذه الصفحة هي ⁨PHPStan⁩ المستوى 10، وهي خاصية تهيئة ترتكز عليها إثباتات التكافؤ، لا قياس.

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

  • الطفرة — تغيير صغير واحد متعمّد على الشيفرة، يُستخدم لاختبار ما إذا كانت مجموعة الاختبارات ستلاحظ ذلك التغيير.
  • الطفرة المقتولة — طفرة جعلت اختبارًا واحدًا على الأقل يفشل؛ فقد أُكّد السلوك فعلًا.
  • الطفرة الهاربة — طفرة تركت كل اختبار ناجحًا. نُفِّذ السلوك لكنه لم يُؤكَّد قط — نقطة ضعف يجب إصلاحها.
  • الطفرة المتكافئة — طفرة لا يمكنها تغيير السلوك القابل للملاحظة، لذا لا يستطيع أي اختبار قتلها. يثبت ⁨NextPDF⁩ هذه الطفرات ويسجّلها في السجلّ.
  • ⁨MSI⁩ (مؤشر درجة الطفرات) — تقريبًا، الطفرات المقتولة مقسومة على إجمالي الطفرات غير المتكافئة؛ مقياس للكشف، لا للتنفيذ.
  • تغطية الأسطر — مقياس لا يسجّل سوى أن سطرًا قابلًا للتنفيذ قد نُفِّذ أثناء المجموعة؛ مُعرَّف بواسطة ⁨PHPUnit⁩، وغير كافٍ بمفرده.