الاختبار الفارغ.
الاختبار الفارغ هو طريقة اختبار تحتوي على جسم خالٍ من أي عبارات قابلة للتنفيذ، وبالتالي ينجح دائماً دون التحقق من أي شيء.
##العلامات والأعراض
يوجد اختبار ويتم الإبلاغ عنه على أنه ناجح، ولكن جسمه لا يحتوي على كود قابل للتنفيذ — لا تهيئة، ولا تشغيل، ولا تحقق. الأشكال الدالة:
// جسم فارغ تماماً
test('calculates tax', () => {});
// تعليق TODO / تعليق عنصر نائب فقط
it('should reject invalid input', () => {
// TODO: اكتب هذا بمجرد استقرار واجهة برمجة التطبيقات (API)
});
// تم تحويل كل شيء إلى تعليق
it('parses the auth header', () => {
// const result = parse(header);
// expect(result).toEqual(expected);
});
كيف تكتشفه:
- يعد اسم الاختبار بسلوك معين، ولكن جسم الاختبار يحتوي على
{}أو مسافات فارغة أو تعليقات فقط. - يظهر ملخص بيئة التشغيل الاختبار كـ ناجح (أخضر)، وليس كمتخطى/معلق — وهذا هو ما يجعله خطيراً.
- تكون تغطية الكود للميزة المسماة صفراً بشكل مريب على الرغم من وجود "اختبار لها".
- أثناء المراجعة، يضيف الفارق (diff) حالة اختبار ولكن بدون استدعاء
expect/assertcall.
مرتبط ولكنه متميز: الاختبار الذي يحتوي على تهيئة ويستدعي النظام تحت الاختبار ولكنه لا يجري أي تحقق يمثل رائحة الاختبار الخالي من التحقق / الاختبار المجهول. بينما الاختبار الفارغ هو الحالة الأكثر تطرفاً حيث يكون جسم الاختبار خالياً تماماً من أي عبارات.
##أسباب المشكلة
لماذا يحدث ذلك
- تم إنشاء هيكل الاختبار كعنصر نائب (مثل أسلوب TDD "اكتب الاسم أولاً") ولم يتم ملؤه أبداً.
- تم تحويل الكود إلى تعليق لتصحيح خطأ أو لإسكات اختبار غير مستقر، ثم تم رفعه ونسيانه.
- أنتج مولد الأكواد أو محرّر الأكواد (IDE) اختباراً هيكلياً فارغاً لم يكمله أحد.
- أراد المطور "حجز" اسم اختبار لوقت لاحق ولكنه استخدم جسماً فارغاً بدلاً من علامة todo صريحة.
لماذا يضر ذلك
- الثقة الزائفة (الجزء الأسوأ). تبلغ معظم بيئات تشغيل الاختبارات عن نجاح الاختبار الفارغ كـ ناجح، وليس كمتخطى. ستظهر جميع بيئات التشغيل مثل JUnit وJest وVitest وغيرها لوناً أخضر لـ
test('x', () => {}). تبدو مجموعة الاختبارات وكأنها تغطي السلوك، ولكن لن يتم التقاط أي تراجع في كود الإنتاج أبداً — وهو أمر ربما يكون أسوأ من عدم وجود اختبار على الإطلاق، لأن علامة النجاح الخضراء تضلل بشكل نشط. - سهولة القراءة. يرى القارئ اختباراً باسم
should reject invalid inputويفترض منطقياً أن هذا السلوك قد تم التحقق منه. يوثق الاسم نية لا يقدمها جسم الاختبار أبداً. - سهولة الصيانة. تضخم الاختبارات الفارغة عدد الاختبارات وتصور التغطية بالاسم، مما يخفي الفجوات الحقيقية ويجعل من الصعب التمييز بين العناصر النائبة المقصودة وتلك المهجورة.
- يضعف الثقة. بمجرد أن يدرك المساهمون في الكود أن "النجاح" لا يعني "التحقق من الصحة"، تضعف مصداقية مجموعة الاختبارات بأكملها.
##المعالجة
قرر ما إذا كان يجب أن يكون الاختبار موجوداً بالفعل، ثم اجعل بيئة التشغيل تظهر الحقيقة.
1. إذا كان يجب اختبار السلوك الآن — املأ جسم الاختبار. أضف خطوات الترتيب والتنفيذ والتحقق (arrange/act/assert) بحيث يختبر الكود فعلياً:
// قبل — ناجح (أخضر)، ولكنه لا يتحقق من شيء
test('rounds half up', () => {});
// بعد — توقع حقيقي
test('rounds half up', () => {
expect(round(2.5)).toBe(3);
});
2. إذا كان عنصراً نائباً حقيقياً — ضع عليه علامة معلق صريحة بحيث تبلغ بيئة التشغيل عنه كـ todo/skipped (مرئي، وليس أخضر كاذباً) بدلاً من ترك جسمه فارغاً. فـtodo الذي يذكر تذكرة التتبع الخاصة به عنصر نائب مرئي وصادق؛ أما التخطي (skip) دون سبب أو تذكرة فلا يفعل سوى استبدال هذه الرائحة بـالاختبار المتجاهل:
// قبل
it('should reject invalid input', () => {
// TODO
});
// بعد — يظهر كـ todo في التقرير، ولا يظهر أبداً كـ "ناجح"
it.todo('should reject invalid input (PROJ-123)'); // Jest / Vitest — اذكر تذكرة التتبع
// test.skip(...) / xit(...) فقط مع سبب مذكور وتذكرة تتبع، وإلا فهو اختبار متجاهل
3. إذا كان الاختبار قديماً وغير مستخدم — فاحذفه. الاختبار المحذوف يعبر عن الحقيقة؛ بينما الاختبار الفارغ كذبة تكلف صيانة.
4. استعد المنطق المعلق. إذا كان جسم الاختبار معلقاً بالكامل، فإما أن تلغي التعليق وتصلحه أو تزيل الاختبار؛ ولا تشحن أبداً كود اختبار معلقاً كـ "تنفيذ".
5. أضف حماية. فعّل قاعدة فحص وجود التحقق (assertion-presence lint rule) (انظر كواشف الرائحة) في التكامل المستمر (CI) بحيث تفشل الاختبارات الفارغة/الخالية من التحقق في البناء بدلاً من النجاح بصمت.
##أدوات الكشف
- eslint-jest jest/expect-expect — eslint-plugin-jest: يحدد الاختبارات التي لا تحتوي على أي استدعاء لـ expect()، فيلتقط الأجسام الفارغة وتلك التي لا تحوي سوى TODO أو كوداً معلقاً على حد سواء
- eslint-vitest vitest/expect-expect — eslint-plugin-vitest: الفحص نفسه في Vitest، إذ يجب أن يحتوي كل اختبار على استدعاء واحد على الأقل لـ expect()
- eslint no-empty-function — ESLint core: no-empty-function — لا يلتقط إلا دالة رد النداء الفارغة حرفياً مثل () => {}؛ فالدوال التي تحتوي على تعليق لا تُعدّ فارغة، لذا تفلت منه الأجسام التي لا تحوي سوى TODO أو كوداً معلقاً (استخدم expect-expect لهذه الحالات)
- sonar javascript:S2699 — SonarSource: "يجب أن تحتوي الاختبارات على تحققات" (Blocker؛ يحدد الاختبارات الخالية من التحقق والاختبارات الفارغة؛ وتوجد مكافئات لـ Java/C#/Python)
- pmd UnitTestShouldIncludeAssert — PMD 7 (Java): UnitTestShouldIncludeAssert، المعروفة سابقاً باسم JUnitTestsShouldIncludeAssert (تحدد اختبارات JUnit/TestNG الخالية من التحقق، بما فيها الاختبارات الفارغة)