التأكيد الزائد.
التأكيد الزائد هو تأكيد يقارن قيمة بنفسها أو بقيمة حرفية مساوية لها بالبناء، فتكون نتيجته محددة مسبقاً ولا يمكنه أن يفشل أو يكتشف انحداراً.
##العلامات والأعراض
تأكيد زائد هو التأكيد الذي تُحدَّد نتيجته قبل أن يعمل الكود قيد الاختبار — المعاملان المتوقع والفعلي نفس القيمة، أو كلاهما قيم حرفية معروفة بالتساوي (أو عدم التساوي). يُعرّفه فهرس xUnit/Test Smells (Peruma وآخرون) بأنه «طريقة اختبار تحتوي على عبارة تأكيد تكون فيها المعاملات المتوقعة والفعلية متطابقة»، ويُلاحظ أن التأكيد لذلك «إما صحيح دائماً أو خاطئ دائماً.»
كيف تتعرف عليه:
assertEquals/toBe/toEqualيتشاركان نفس التعبير أو المتغير كحجتين.- قيمة حرفية منطقية مؤكدة على نفسها، مثل
assertTrue(true)أوexpect(true).toBe(true). - مقارنة يستطيع المصرِّف/المُدقق إثبات ثباتها، كـ
expect(x === x).toBe(true). - «فحص سلامة» يُعيد صياغة ثابت أعلنته للتو بدلاً من تجربة النظام.
// رائحة: النتيجة ثابتة، الكود قيد الاختبار لا يشارك أبداً
test('user is active', () => {
expect(true).toBe(true); // يجتاز دائماً
const status = 'active';
expect(status).toBe('active'); // يعيد صياغة القيمة الحرفية، لا يُثبت شيئاً
expect(user.id).toEqual(user.id); // قيمة مقارنة بنفسها
});
علامة دالة موثوقة: يمكنك حذف كود الإنتاج كلياً والتأكيد يجتاز أخضراً.
##أسباب المشكلة
لماذا يحدث
- تصحيح متبقٍّ. يُلاحظ الفهرس صراحةً أن هذه الرائحة «يُدخلها المطورون لأغراض التصحيح ثم ينسونها» — عنصر نائب مُرمَّز مثل
assertTrue(true)يبقى حتى عملية الحفظ. - انجراف النسخ-اللصق / إعادة الهيكلة. تُستبدل متغير في كلا طرفي
assertEquals، أو تُعاد تسمية قيمة قيد الاختبار فيتطابق الطرف المتوقع والطرف الفعلي على الرمز نفسه. - تطابق بالبناء. تأكيد قيمة مقابل القيمة الحرفية التي عُيِّنت منها للتو، بدلاً من التأكيد مقابل توقع مُشتق باستقلالية.
- مسرحية التغطية. يُضاف تأكيد لمجرد تلبية قواعد «كل اختبار يجب أن يؤكد»، دون التحقق من أي شيء ذي معنى.
لماذا يضر
- ثقة زائفة. الاختبار أخضر دائماً ويُحسب ضمن حجم المجموعة والتغطية، ولكنه لا يتحقق من شيء. لا يستطيع اكتشاف انحدار، فيُخفي ثغرات في شبكة الأمان.
- الموثوقية بلا معنى. اختبار لا يمكنه أن يفشل لا يُعطي أي إشارة؛ واختبار خاطئ دائماً عبء ميت يُتجاهل أو يُتخطى بـ
skip. - قابلية القراءة. يُضيع القراء جهداً في إعادة بناء السلوك المقصود من تأكيد يؤكد حقيقة بديهية؛ لم يعد الاختبار يوثّق متطلباً.
- قابلية الصيانة. تتراكم التأكيدات الزائدة كضجيج، وتُضخّم المقاييس، وتتآكل الثقة في المجموعة، مما يشجع الناس على التوقف عن قراءة التأكيدات بعناية.
##المعالجة
استبدل الحقيقة البديهية بفحص يربط قيمة متوقعة معروفة باستقلالية بـالنتيجة الفعلية التي ينتجها النظام قيد الاختبار.
الخطوات:
- حدد التأكيد ذا النتيجة الثابتة — نفس المعامل على كلا الجانبين، أو قيمتان حرفيتان متساويتان.
- حدد النية الحقيقية. ما السلوك الذي كان هذا الاختبار يُقصد التحقق منه؟ إن لم يكن ثمة شيء، فالتأكيد (أو الاختبار بأكمله) ميت ويجب حذفه.
- أكّد على مخرجات SUT، لا على المدخلات. أدخل للنظام مدخلات حقيقية وقارن نتيجته المحسوبة بقيمة متوقعة مُرمَّزة محسوبة يدوياً — لا بإحدى مدخلاته/متغيراته الخاصة.
- أبقِ المتوقع/الفعلي متمايزَين. تأكد أن معامل التوقع ثابت كتبته بقصد وأن المعامل الفعلي هو القيمة المُعادة من الكود قيد الاختبار (يُصلح أيضاً رائحة ترتيب الحجج الخاطئة المرتبطة).
- أعِد التشغيل مع كسر التنفيذ (غيّره) للتأكد من أن التأكيد يمكنه أن يفشل فعلاً.
// قبل — زائد: النتيجة ثابتة
test('discount', () => {
const total = 100;
expect(total).toBe(100); // يعيد صياغة القيمة الحرفية
expect(applyDiscount).toBe(applyDiscount); // قيمة مقارنة بنفسها
});
// بعد — ذو معنى: مدخل معروف -> نتيجة متوقعة مستقلة
test('applies a 10% discount', () => {
expect(applyDiscount(100, 0.1)).toBe(90); // نتيجة SUT مقارنةً بقيمة متوقعة محسوبة يدوياً
});
إن تُرك عنصر نائب مثل assertTrue(true) من التصحيح، احذفه؛ وإن كان بديلاً لفحص حقيقي، اكتب ذلك الفحص.
##أدوات الكشف
- sonar javascript:S5863 — يجب ألا تُعطى التأكيدات الحجة ذاتها مرتين (تأكيدات Chai فقط)
- sonar java:S5863 — يجب ألا تُعطى التأكيدات الحجة ذاتها مرتين
- eslint no-self-compare — يمنع المقارنات التي يتطابق طرفاها تمامًا (x === x)؛ ولا يلتقط expect(x).toEqual(x). ولا تُبلغ قاعدة Sonar S5863 عن ذلك إلا في مجموعات الاختبار المكتوبة بـ Chai (assert.equal(x, x)، expect(x).to.equal(x))، لذا تحتاج المقارنات الذاتية في Jest/Vitest إلى مراجعة يدوية