اختبار الطفرات في C++: عندما تكون تغطية الكود بنسبة 100% كذبة
\n\nهل تفتخر بتغطية الكود بنسبة 95%؟ ربما تفعل ذلك دون داعٍ. تغطية الكود تشبه قفلًا يتم فحصه بمجرد هز المقبض. أما اختبار الطفرات فهو لص يحاول فعليًا فتح الخزنة. في هذه المقالة، سنشرح كيف يحول اختبار الطفرات في C++ اختباراتك من مجرد إجراء شكلي إلى سلاح حقيقي. ونعم، لن نقوم بنسخ التعريفات المملة من ويكيبيديا.
\n\nتخيل: لقد كتبت دالة، وغطيتها بالاختبارات، وCI أخضر. ثم يحدث خطأ في الإنتاج. ألم مألوف؟ غالبًا لا تكمن المشكلة في الكود، بل في الاختبارات التي لا تتحقق مما يجب. Mutation testing لـ C++ يحل هذه المشكلة تحديدًا - فهو يُظهر مدى خطورة اختباراتك على الأخطاء.
\n\nما هو اختبار الطفرات ولماذا يحتاجه مطور C++؟
\n\nاختبار الطفرات (أو mutation testing) هو أسلوب لتقييم جودة الاختبارات عن طريق إدخال تغييرات صغيرة (طفرات) في الكود المصدري. إذا استمرت الاختبارات في النجاح بعد الطفرة، فهذا يعني أنها ضعيفة. الطفرة نجت. إذا فشلت الاختبارات، فقد قُتلت الطفرة، والاختبار يعمل.
\n\nهذا مهم بشكل خاص لـ C++ للأسباب التالية:
\n- \n
- غالبًا ما يحتوي كود C++ على منطق معقد مع المؤشرات والقوالب و RAII. \n
- من السهل خداع مقاييس التغطية القياسية (line coverage, branch coverage). \n
- تكلفة الخطأ في C++ (خاصة في الأنظمة المدمجة والمالية ومحركات الألعاب) قد تكون كارثية. \n
مثال بسيط: لديك دالة للتحقق من العمر:
\n\nbool is_adult(int age) {\n return age >= 18;\n}\n\nالاختبار:
\n\nTEST(AdultTest, Positive) {\n EXPECT_TRUE(is_adult(20));\n}\n\nالتغطية: 100% من الأسطر. لكن الطفرة age >= 18 → age > 18 لن يتم اكتشافها، لأن 20 لا تزال تمر. الطفرة نجت. أما لو كان لديك اختبار لـ is_adult(18) و is_adult(17) - لكنت قد التقطت الخطأ. اختبار الطفرات في C++ يدفعك لكتابة مثل هذه الاختبارات الحدودية.
عوامل الطفرات الرئيسية في C++
\n\nتطبق أدوات اختبار الطفرات مجموعة من العوامل (mutators). إليك الأكثر شيوعًا لـ C++:
\n\n- \n
- استبدال العوامل الحسابية:
+→-,*→/\n - استبدال العوامل المنطقية:
&&→||,==→!=\n - تغيير الشروط الحدودية:
>=→>,<→<=\n - حذف التعبيرات: استبدال استدعاء دالة بثابت \n
- تغيير القيمة المرجعة:
return true→return false\n - طفرة الحلقات: تغيير شرط الخروج أو الخطوة \n
مثال مع كود حقيقي:
\n\nint factorial(int n) {\n if (n <= 1) return 1;\n return n * factorial(n - 1);\n}\n\nالطفرة #1: n <= 1 → n < 1. إذا استدعى الاختبار factorial(1) وتوقع 1، لكن الطفرة تزيل الحالة الأساسية - سيفشل الاختبار فقط إذا كنت تتحقق من factorial(0) أو factorial(1) بشكل منفصل.
الطفرة #2: return n * factorial(n - 1) → return n + factorial(n - 1). إذا كانت الاختبارات فقط على الأرقام الموجبة، فقد لا تلاحظ استبدال الضرب بالجمع.
هذه هي الحالات التي يكشفها mutation testing في C++.
\n\nأدوات اختبار الطفرات في C++
\n\nليست كل الأدوات مفيدة بنفس القدر. إليك ثلاثة لاعبين رئيسيين في سوق C++:
\n\n1. Mull
\nمشروع حديث ونشط. يعمل بناءً على LLVM pass. يدعم C++17/20، ويتكامل مع Google Test و Catch2 و doctest