تصميم الاختبارات

اختبارات التطفير سلبية. واختبارات TDD إيجابية.

يُسمّى كلاهما اختبارًا، لكنهما يؤكدان أمرين متعاكسين. اختبار TDD يقرر ما يجب أن يفعله النظام، أما تشغيل التطفير فلا يستطيع إلا الإبلاغ عمّا لم تلاحظه مجموعة اختباراتك. ومعرفة أي قطب تحمله تحدّد ما تفعله بالنتيجة، وتحدّد كذلك إن كان يمكنك الثقة باختبار كتبه وكيل.

نوعان من التأكيد

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

مجموعة TDD خضراء هي مجموعة تصريحات عن النوايا. ودرجة تطفير عالية هي غياب نوع معيّن من الدليل ضد مجموعتك. واحد فقط من هذين مواصفة.

الاختبار الإيجابي

  • + يُكتب قبل الكود، فيوجد المطلب بالكلمات قبل أن يوجد في المنطق
  • + يفشل أولًا، وهذا الدليل الرخيص الوحيد على أن الاختبار قادر على الفشل أصلًا
  • + يسمّي قاعدة، فينجو من إعادة الهيكلة: يمكن للداخليات أن تتحرك دون أن يتحرك التأكيد
  • + يضع ضغطًا على التصميم بينما لا يزال تغييره رخيصًا
  • + يُقرأ كتوثيق من الشخص التالي، ومن الوكيل التالي أيضًا

الفحص السلبي

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

الكلمة نفسها، وأداتان مختلفتان

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

كيف تشغّله فعلًا

لاختبار التطفير سمعة بأنه فوق الطاقة، وهي في الغالب مشكلة نطاق لا مشكلة أدوات. هذا الترتيب يبقيه رخيصًا بما يكفي ليصمد أمام جدول التسليم.

1

أرسِ التغطية أولًا

  • - التطفير على كود غير مغطّى لا يبلّغ إلا بالبديهي، وبثمن مرتفع
  • - اجعل التغطية البوابة المستمرة الرخيصة، وأبقِ التطفير للكود المغطّى أصلًا
  • - اختر وحدة واحدة مهمة — المدفوعات أو الصلاحيات أو التسعير — لا المستودع بأكمله
2

حدّد نطاق كل تشغيل

  • - تحليل التغطية لكل اختبار هو أكبر رافعة: لا يشغّل إلا الاختبارات التي تصل إلى كل طفرة
  • - في طلبات الدمج، طفِّر الملفات المتغيرة فقط بالنمط التدريجي؛ فذلك تشغيل يُقاس بالدقائق
  • - أبقِ المسح الشامل مجدولًا وخارج المسار الحرج، حيث لا تكلّف مدته أحدًا شيئًا
3

صنّف الطفرات الباقية في ثلاث سلال

  • - مطلب ناقص، يصبح اختبارًا إيجابيًا جديدًا يُسمّى باسم القاعدة
  • - طفرة مكافئة لا تغيّر شيئًا مرصودًا، تُوثَّق وتُستثنى مرة واحدة
  • - كود لا يطلبه أي مطلب، فيُحذف — وهذا أرخص قتل ممكن
4

ضع البوابة على التراجع لا على رقم

  • - اضبط عتبة break تمنع انحدار الدرجة، وتوقّف عند ذلك
  • - أبلغ عن الطفرات الباقية كبنود مراجعة لا كإخفاقات بناء، حتى لا يتعلّم أحد تجاهل بناء أحمر
  • - تابع زمن التشغيل كمقياس من الدرجة الأولى؛ فالبوابة التي تُعطَّل لبطئها لا تحمي شيئًا

ما يتغيّر عندما يكتب نموذج لغوي الكود

لم تخلق الوكلاء هذه المشكلة، لكنها صنّعتها على نطاق واسع. ومسألة القطب تكفّ عن كونها فلسفة في اللحظة التي يصبح فيها لاختباراتك وتنفيذك المؤلف نفسه، وذلك المؤلف نموذج.

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

أدخِله في الحلقة لا في تقرير

الطفرة الباقية تغذية راجعة جيدة بشكل غير معتاد للوكيل: قابلة للتنفيذ، ومحددة، ولا يمكن التذرّع للتخلص منها. فقرة نصائح المراجعة يُقرّ بها ثم تُهمَل، أما تغيير محدد مرّ دون كشف فيُصلَح. وهذا بالضبط شكل دور hardener في SwarmForge، الذي يشغّل أداة التطفير ملفًا ملفًا ولا يُسمح له بالتقدّم قبل معالجة الباقين.

انقل المعيار إلى تعليمة الكتابة

أفضل سطر في ذلك المشروع موجود في مطالبة دور coder: اكتب اختبارات تفشل أمام تنفيذ خاطئ معقول. هذا معيار تطفير مدمج في خطوة التوليد، وهو أرخص كثيرًا من اكتشاف الأمر نفسه في تدقيق بعد ساعة. ضع هذه الجملة في تعليمات وكيلك.

افصل المؤلف عن المُصلِّب

النموذج نفسه، في السياق نفسه، سيشرح لك لماذا الطفرة الباقية مقبولة، لأن الاستدلال الذي أنتج الفجوة لا يزال أمامه. شغّل مرور التطفير كخطوة منفصلة بسياق منفصل وبلا وصول إلى التبرير الأصلي — الفرق البرمجي ومُخرَج الأداة فقط.

توقّع مطاردة الدرجة بسرعة الآلة

إذا طُلب من وكيل رفع درجة التطفير، فسيكتب كواشف تغيير تقتل الطفرات أسرع من قدرة أي أحد على مراجعتها. وقانون غودهارت أسوأ مع الوكلاء لأن الحجم مجاني. اجعل الوكلاء يبلّغون عن الباقين ويقترحون اختبارات على مستوى المتطلبات، وأبقِ قرار «ما هو المطلب» عند إنسان أو عند دور المواصفة.

تقييم كود أنتجه وكيل للتوّ

التغطية ودرجة التطفير معًا معيار عملي لعمل كتبه ذكاء اصطناعي. اقرأهما كزوج، فالمعلومة المثيرة تكمن في اختلافهما.

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

الطفرة الباقية نفسها، واستجابتان

هنا يتوقف الحديث عن القطب ليصبح عملًا. تقرير التطفير يعطيك موقعًا، وما تكتبه بعده يحدد إن كانت المجموعة ستزداد قوة أم مجرد تصلّبًا.

الكود والطفرة التي تبقى

js

إزالة ‎.trim()‎ طفرة معتادة على نداء الدوال. تبقى حية كلما لم تحمل أي بيانات تجريبية مسافات محيطة، وهذا حال معظمها، بما فيها ما اختلقه نموذج لكوده الخاص.

// importer.js
const normalise = (value) => value.trim().toLowerCase();

export function importRows(rows) {
	return rows.map((row) => ({ email: normalise(row.email) }));
}

// Surviving mutant: normalise() with .trim() removed.
// The suite passes either way, so the report flags it.

قتل الطفرة وربط المجموعة بالتنفيذ

js

هذا الاختبار يقتل الطفرة الباقية، لكنه يجمّد أيضًا التنفيذ الحالي: على normalise أن تبقى قابلة للوصول وأن تُنادى بهذا العدد بالضبط. أعِد تسميتها أو ادمجها أو انقلها خلف حدّ، وسينكسر الاختبار مع أن السلوك لم يتغير. والوكيل الذي يحسّن الدرجة ينتج هذا الشكل تلقائيًا.

it('calls normalise once per row', () => {
	const spy = vi.spyOn(internals, 'normalise');
	importRows([{ email: ' Ada@Example.COM ' }]);
	expect(spy).toHaveBeenCalledTimes(1);
});

// Green. Mutant dead. Score up.
// Nothing here states what an imported email address should look like.

الإجابة على السؤال الذي طرحته الطفرة

js

الطفرة نفسها والقتل نفسه، لكن التأكيد هنا قاعدة يمكن لمسؤول منتج أن يقرأها. يمرّ عبر الدالة العامة، فتبقى الداخليات حرة الحركة، ويشرح نفسه في رسالة الفشل بعد ستة أشهر.

it('trims and lower-cases every imported email address', () => {
	expect(importRows([{ email: ' Ada@Example.COM ' }]))
		.toEqual([{ email: 'ada@example.com' }]);
});

// Same mutant dead, but the suite gained a specification
// instead of a snapshot of today's call graph.

إبقاء التدقيق رخيصًا بما يكفي لمواصلته

json

تحليل التغطية لكل اختبار هو أكبر رافعة أداء: فهو لا يشغّل إلا الاختبارات التي تصل فعلًا إلى كل طفرة. والوضع التدريجي يبقي تشغيل طلب الدمج في دقائق. وعتبة break أرضية ضد التراجع، لا هدفًا يُطارد.

{
	"testRunner": "vitest",
	"coverageAnalysis": "perTest",
	"incremental": true,
	"mutate": ["src/**/*.js", "!src/**/*.test.js"],
	"thresholds": { "high": 80, "low": 60, "break": 60 }
}

البوابة لفرع كتبه وكيل

sh

سؤالان مستقلان تطرحهما خطوة لم تكتب الكود: هل هذا الكود بشكل يسمح بتغييره؟ وهل تتفاعل الاختبارات التي تحميه عندما يتغير؟ تُنشر الإجابتان على طلب الدمج، ولا واحدة منهما قابلة للتفاوض من المؤلف.

CHANGED=$(git diff --name-only origin/main... -- 'src/**/*.js')

# structural: complexity against coverage on touched methods
crap-report --changed-only --threshold 30 $CHANGED || exit 1

# behavioural: do the new tests notice anything?
stryker run --incremental --mutate "$CHANGED"

# survivors are review items with a named requirement attached,
# never a licence to write a test that pins the call graph.

حين يصبح السلبي سامًّا

مطاردة الدرجة

بمجرد أن تصبح درجة التطفير هدفًا، تُكتب اختبارات لقتل الطفرات بدلًا من إعلان المتطلبات. تمرّ في المراجعة لأنها خضراء، وهي بالضبط التي تنكسر في إعادة الهيكلة التالية مع أن السلوك لم يتغير.

محاربة الطفرات المكافئة

بعض الطفرات تغيّر الكود دون تغيير السلوك المرصود، ولا يمكن لأي اختبار صادق قتلها. وثّقها واستثنِها وامضِ. الوقت المصروف هنا يشتري رقمًا لا ثقة.

تأكيد الطفرة لا القاعدة

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

ترك المؤلف يُصلّب عمله

النموذج الذي كتب الكود سيجد لكل طفرة باقية سببًا لقبولها، لأن استدلاله لا يزال يبدو له سليمًا. فالتصليب يجب أن يحدث في خطوة لا ترى إلا الفرق البرمجي ومُخرَج الأداة.

حوّل كل طفرة باقية إلى تصريح إيجابي

اقرأ الطفرة الباقية كسؤال

  • - أي سلوك يجب أن يكون صحيحًا كي يكسر هذا التغيير شيئًا؟
  • - أي مطلب سيفشل الآن لو أن أحدًا كتبه؟
  • - من في المراحل التالية سيلاحظ لو وصلت هذه الطفرة إلى الإنتاج؟

اكتب الجواب لا القتل

  • - سمِّ الاختبار باسم القاعدة، لا باسم الطفرة ولا برقم السطر
  • - أكِّد عبر السطح العام لتبقى الداخليات حرة في التغيير
  • - إن لم يمكن الوصول إلى القاعدة إلا بكشف الداخليات، فتلك نتيجة تصميم لا نتيجة اختبار

أو احذف الكود

  • - إن لم يمكن تسمية أي مطلب، فلا شيء يعتمد فعلًا على هذا السلوك
  • - الطفرة الباقية في كود غير موصَّف غالبًا مطلب ميت أكثر من كونها اختبارًا ناقصًا
  • - الحذف أرخص قتل ممكن، ويجعل التدقيق التالي أسرع

أحدهما يكتب المواصفة والآخر يدقّقها

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

وهذا التوزيع أهم اليوم مما كان حين كانت المهمتان لمهندس واحد. فما زال ينبغي أن تُولد الاختبارات عبر TDD كتأكيدات إيجابية عن السلوك، بما في ذلك عندما يكتبها وكيل، ولهذا فإن تعليمة «اكتب اختبارات تفشل أمام تنفيذ خاطئ معقول» تنتمي إلى مطالباتك. ثم تدقّق تشغيلات التطفير تلك التأكيدات من سياق منفصل لا يُجادَل. ولا تذهب أي طفرة باقية إلى اختبار مباشرة: ترجمها أولًا إلى مطلب، أو احذف الكود الذي تسكنه. فالنتيجة السلبية لا تصبح قيمة باقية إلا حين يحوّلها أحدهم إلى تصريح إيجابي عن الغرض من البرمجية.

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

هل اختبار التطفير أفضل من تغطية الكود؟

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

كيف يساعد اختبار التطفير مع الكود المولّد بالذكاء الاصطناعي؟

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

هل ينبغي للوكلاء تشغيل اختبار التطفير بأنفسهم؟

نعم، كخطوة منفصلة عن التي كتبت الكود، ومع معاملة الطفرات الباقية كأسئلة لا كدرجة تُعظَّم. دع الوكيل يبلّغ عن الباقين ويقترح اختبارات على مستوى المتطلبات، وأبقِ قرار ماهية المطلب عند إنسان أو دور مواصفة، وإلا حصلت على كواشف تغيير بسرعة الآلة.

أي درجة تطفير ينبغي أن نستهدف؟

تُعدّ الدرجات فوق 80 بالمئة قوية عادة، و60 إلى 80 بالمئة صالحة مع فجوات حقيقية، لكن الرقم لا يعني شيئًا إلا على مستوى الوحدة. القاعدة الأنفع هي ألا يوجد تراجع في الملفات المتغيرة، مع عتبة break تمنع انحدار الدرجة.

ما الأدوات التي تستخدمها الفرق؟

PIT هو الخيار الراسخ على منصة JVM، ويغطي Stryker Mutator لغات JavaScript وTypeScript وC# وScala، وفي بايثون الخياران المعتادان mutmut وcosmic-ray. وكلها تدعم تقييد التشغيل بالملفات المتغيرة، وهذا ما يجعل الممارسة محتملة الكلفة على مستوى طلب الدمج.

أين يقع اختبار التطفير في التكامل المستمر؟

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

© 2026 Ryware Solutions Ltd. · رقم الشركة 516681764