الاختبار الطفري في C#: نكسر الكود لنجعله أفضل

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

عندما تنجح الاختبارات ولكن الكود لا يزال "مكسورًا"

\n\n

تخيل الموقف: لقد كتبت اختبارات وحدة لمشروع C#، dotnet test يعطي ضوءًا أخضر، وتغطية الكود (code coverage) تبلغ 85%. الفريق راضٍ، وCI/CD يعمل بشكل مثالي. ولكن بعد شهر، يظهر خطأ في بيئة الإنتاج (production) كان من المفترض أن تلتقطه اختبارات الوحدة. هل يبدو الأمر مألوفًا؟

\n\n

المشكلة هي أن تغطية الكود (code coverage) هي مقياس خادع. إنها تظهر أي أسطر من الكود تم تغطيتها، ولكنها لا تتحقق من جودة الاختبارات نفسها. يمكن أن يكون لديك تغطية 100%، ولكن بدون أي assert ذي معنى. وهنا يأتي دور الاختبار الطفري في C# - وهي منهجية "تكسر" الكود الخاص بك للتحقق من مدى قدرة اختباراتك على اكتشاف الأخطاء.

\n\n

في هذه المقالة، سنشرح ما هو الاختبار الطفري (mutation testing)، وكيفية تطبيقه في مشاريع .NET باستخدام Stryker.NET، ولماذا هو أمر لا غنى عنه لأي مطور C# جاد.

\n\n

ما هو الاختبار الطفري (Mutation Testing)؟

\n\n

الاختبار الطفري هو طريقة لتقييم جودة الاختبارات، تعتمد على إدخال تغييرات صغيرة متعمدة (طفرات) في الكود المصدري للبرنامج. كل نسخة معدلة من الكود تسمى طافرة (mutant).

\n\n

المنطق بسيط:

\n
    \n
  • يتم أخذ كود الإنتاج الخاص بك.
  • \n
  • يتم إنشاء الطفرات تلقائيًا - على سبيل المثال، يتم استبدال العامل + بـ -، ويتم تغيير الشرط if (x > 5) إلى if (x >= 5)، و true إلى false.
  • \n
  • يتم تشغيل اختبارات الوحدة الخاصة بك ضد كل طافرة.
  • \n
  • إذا فشل الاختبار - يتم "قتل" الطافرة (killed). هذا اختبار جيد.
  • \n
  • إذا نجح الاختبار - "تنجو" الطافرة (survived). هذه علامة سيئة: اختبارك لم يلاحظ تغيير المنطق.
  • \n
\n\n

الهدف هو تحقيق نسبة عالية من الطفرات المقتولة (عادةً 80%+). هذا ضمان حقيقي بأن اختباراتك تتحقق من السلوك، وليس فقط "تمر" عبر الكود.

\n\n

لماذا تغطية الكود (code coverage) غير كافية؟

\n\n

لننظر إلى مثال بسيط بلغة C#. لنفترض أن لدينا طريقة تتحقق مما إذا كان الرقم زوجيًا:

\n\n
public class MathUtils\n{\n    public bool IsEven(int number)\n    {\n        return number % 2 == 0;\n    }\n}
\n\n

واختبار "مثالي" بتغطية 100%:

\n\n
[Test]\npublic void IsEven_ShouldReturnTrueForEvenNumber()\n{\n    var utils = new MathUtils();\n    var result = utils.IsEven(4);\n    Assert.IsTrue(result);\n}
\n\n

الاختبار ناجح، والتغطية 100%. ولكن ماذا لو قام شخص ما عن طريق الخطأ (أو عن قصد) بتغيير الطريقة إلى return number % 2 == 1;؟ هل سيفشل اختبارنا الوحيد؟ لا! سيتحقق من الرقم 4 (4 % 2 == 0، ولكن الطريقة سترجع false الآن)، سيفشل assert - سيتم قتل الطافرة. كل شيء على ما يرام؟ انتظر.

\n\n

ماذا لو تم تغيير الكود إلى return number % 2 != 1;؟ منطقيًا هذا هو نفس الشيء، ولكن الاختبار الطفري سينشئ طافرة: return number % 2 != 0;. سيظل اختبارنا مع الرقم 4 ناجحًا (4 % 2 != 0 — false، الطريقة سترجع false، ولكن assert يتوقع true - سيفشل الاختبار). هذا جيد. لكن تخيل أننا كتبنا الاختبار بهذه الطريقة:

\n\n
[Test]\npublic void IsEven_ShouldNotThrow()\n{\n    var utils = new MathUtils();\n    Assert.DoesNotThrow(() => utils.IsEven(4));\n}
\n\n

التغطية لا تزال 100%، ولكن assert لا يتحقق من النتيجة! الآن أي طافرة ستبقى على قيد الحياة. سيظهر الاختبار الطفري على الفور أن اختباراتك فارغة.

\n\n

أدوات الاختبار الطفري في C#

\n\n

الأداة الرئيسية والقياسية فعليًا لـ .NET هي Stryker.NET. إنه إطار عمل مفتوح المصدر (open-source) يسهل دمجه في أي مشروع C#. وهو يدعم xUnit و NUnit و MSTest.

\n\n

أدوات أخرى (أقل شهرة):

\n
    \n
  • VisualMutator - إضافة قديمة لـ Visual Studio.
  • \n
  • Nester

المدونات

توصيات الكتب