الاختبار المتجاهل.
اختبار تم رفعه إلى كود المشروع ولكنه لا يعمل أبداً لأنه تم تخطيه، أو تعطيله، أو تحويله إلى تعليق، مما يعطي مظهراً كاذباً بالتغطية دون التحقق من أي شيء فعلياً.
##Signs and Symptoms
يوجد اختبار في المجموعة ولكنه يُمنع بشكل دائم من التنفيذ. لا يزال يُحسب كـ "اختبار" في الملف، ولكنه يسهم بصفر من التحقق. راقب علامات التخطي/التجاهل، والكتل المسبوقة بحرف x، والأجسام الفارغة، وكود الاختبار المدفون في التعليقات — وغالباً ما يقترن ذلك بعذر غامض مثل "متقلب (flaky)" أو "الإصلاح لاحقاً".
// معدلات التخطي/التعطيل — لا تعمل أبداً
describe.skip('checkout flow', () => { /* ... */ });
it.skip('applies the discount', () => { /* ... */ });
test.todo('handles expired coupons');
// مسبوقة بـ x — نفس التأثير
xit('rejects negative amounts', () => { /* ... */ });
xdescribe('payments', () => { /* ... */ });
// اختبار محول لتعليق — غير مرئي لبيئة التشغيل والمبلغ
// it('retries on 503', async () => {
// await expect(client.fetch()).resolves.toBeOk();
// });
// JUnit: @Ignore / @Disabled مع عذر واهٍ
@Ignore("disabled for now as this test is too flaky")
@Test public void peerPriority() { /* ... */ }
العلامات الدالة في تقرير الاختبار: وجود عدد غير صفري للاختبارات "المتخطاة"/"المعلقة" لا ينظر إليه أحد، ومجموعات اختبارات تتقلص بصمت بمرور الوقت، وعمليات تخطٍ غير مرتبطة بمشكلة تتبع أو تاريخ انتهاء صلاحية. الإرجاع الشرطي (return) في الجزء العلوي من الاختبار (مثل if (process.platform === 'win32') return;) هو بديل أكثر ذكاءً يخفي التخطي عن عدادات التخطي تماماً.
##Reasons for the Problem
لماذا يحدث ذلك
- بدأ اختبار ما بالفشل (تراجع حقيقي، أو تبعية توقيت متقلبة، أو تغيير في البيئة/الإصدار) وكان تخطيه هو أسرع طريقة للحصول على بناء ناجح (أخضر) أو إلغاء حظر دمج الأكواد.
- كُتب اختبار قبل التنفيذ الفعلي (باستخدام
test.todo/xitكعنصر نائب) ولم يتم الرجوع إليه وإكماله أبداً. - أدت عمليات الهجرة (إلى إطار عمل جديد، أو بيئة تشغيل جديدة، أو واجهة برمجة تطبيقات جديدة) إلى كسر الاختبار وتم ركنه "مؤقتاً".
- كان المقصود من التخطي أن يستمر لفترة قصيرة؛ ولكن مع عدم وجود تذكير، أو تاريخ انتهاء، أو تذكرة تتبع، أصبح التخطي دائماً.
لماذا يضر ذلك
- الثقة الزائفة. يبدو الاختبار المتخطى كجزء من التغطية في الملف وفارق طلب الدمج (PR diff)، ولكنه لا يشغل شيئاً. أصبح مسار الكود الذي ادعى الاختبار حمايته غير محمي الآن، بينما يفترض الجميع أنه آمن.
- عفن الكود (Bit rot). لا يتم تصريف الاختبار المتجاهل أبداً (في بعض اللغات)، ولا يتم إعادة هيكلته، ولا يتم تحديثه. كلما طال بقاؤه، كلما ابتعد عن الواقع، حتى تصبح إعادة تفعيله مكلفة أكثر من إعادة كتابته.
- التراجعات المخفية. لا يزال الخطأ أو السلوك المتقلب الذي دفع إلى التخطي موجوداً — لقد تم إسكاته فقط. التخطي يعالج العَرَض (السطر الأحمر) بدلاً من علاج المرض.
- الضوضاء والتآكل. تدرب أرقام الاختبارات "المتخطاة" المستمرة الفريق على تجاهل تقرير الاختبار، مما يسمح بمرور عمليات تخطٍ جديدة دون أن يلاحظها أحد. كما أن كود الاختبار الميت والمحول لتعليق يضيف عبء قراءة وصيانة إضافياً دون أي فائدة.
##Treatment
تعامل مع كل اختبار متجاهل كقرار يجب اتخاذه الآن، وليس كأمر يتم تأجيله إلى أجل غير مسمى.
- فرز كل تخطٍ. لكل اختبار معطل/معلق/
todo، قرر: إصلاحه، أو حذفه، أو وضعه في الحجر الصحي مع تاريخ انتهاء وتذكرة تتبع. خيار "تركه متخطى للأبد" ليس خياراً مطروحاً. - الإصلاح وإعادة التفعيل إذا كان السلوك لا يزال مهماً. إذا كان الاختبار متقلباً، فأصلح التقلب (التحكم في الوقت، والعشوائية، والعمليات غير المتزامنة، والحالة المشتركة) بدلاً من تخطيه.
- احذفه إذا كانت الميزة قد أزيلت أو كان الاختبار قديماً. الاختبار المحذوف يعبر عن الصدق; بينما الاختبار المتخطى يكذب. يتذكر نظام التحكم في الإصدارات (Git) الاختبار إذا احتجت إليه مجدداً — لذا لا تقم أبداً بتحويل اختبار إلى تعليق بدلاً من حذفه.
- إذا كان لا بد من التخطي مؤقتاً، فاجعله واضحاً ومحدداً بزمن: قم دائماً بتضمين سبب ورابط تتبع، ويفضل استخدام آلية تظهر في التقارير وتفشل بمجرد انتهاء الموعد النهائي، حتى لا يتعفن التخطي.
- أوقف النزيف باستخدام التحليل الساكن والتكامل المستمر (Lint/CI). قم بتفعيل قاعدة "منع الاختبارات المعطلة" ليتم كشف التخطيات الجديدة في مراجعة الكود، وتعامل مع عدد التخطيات الحالي كعمل متراكم (backlog) يجب خفضه إلى الصفر.
// قبل — صامت، دائم، غير متتبع
it.skip('refunds the full amount on cancel', async () => {
await expect(refund(order)).resolves.toEqual({ amount: 100 });
});
// بعد — تم إصلاحه ويعمل مجدداً (السبب الجذري: المجموع بالفاصلة العائمة)
it('refunds the full amount on cancel', async () => {
await expect(refund(order)).resolves.toEqual({ amount: 100 });
});
// بديل مؤقت مقبول — مرئي، منسوب لسبب، وينتهي صلاحيته
it.skip('refunds the full amount on cancel — flaky clock, see JIRA-1234 (remove by 2026-07-01)', async () => {
/* ... */
});
// قبل
@Ignore("too flaky")
@Test public void peerPriority() { ... }
// بعد: أصلح التبعية الزمنية وأعد تفعيله، أو احذفه إن كان قديماً
@Test public void peerPriority() { ... }
##Detected by
- eslint-jest jest/no-disabled-tests — منع الاختبارات المعطلة
- eslint-jest jest/no-commented-out-tests — منع الاختبارات المحولة إلى تعليقات
- eslint-vitest vitest/no-disabled-tests — منع الاختبارات المعطلة
- eslint-vitest vitest/no-commented-out-tests — منع الاختبارات المحولة إلى تعليقات
- sonar java:S1607 — يجب عدم تجاهل الاختبارات
- sonar javascript:S1607 — يجب عدم تخطي الاختبارات دون تقديم سبب