Test Smells.
Recurring problems in test code — how to recognize each one, why it hurts, how to fix it, and the lint rules that detect it.
يجمع اختبار واحد العديد من التحققات (assertions) غير الموثقة في طريقة واحدة، بحيث عندما يفشل لا يمكنك تحديد أي من التحققات قد فشل أو لماذا - مما يضطرك للمقامرة لمعرفة ذلك.
يتحقق الاختبار المتلهف من عدة طرق أو سلوكيات متميزة للوحدة الخاضعة للاختبار في طريقة اختبار واحدة، بدلاً من التركيز على سلوك واحد.
التأكيد الزائد هو تأكيد يقارن قيمة بنفسها أو بقيمة حرفية مساوية لها بالبناء، فتكون نتيجته محددة مسبقاً ولا يمكنه أن يفشل أو يكتشف انحداراً.
طريقة اختبار تقوم بتشغيل الكود ولكنها لا تحتوي على أي تحقق (assertion)، وبالتالي تنجح طالما لم يتم إلقاء أي استثناء — مما يترك الغرض الفعلي منها وما تتحقق منه مجهولاً.
تتحقق طريقة اختبار واحدة من نفس الشرط أكثر من مرة — بتكرار تحقق متطابق أو إعادة فحص منطق مكافئ — بدلاً من إزالة الفحص الزائد عن الحاجة أو تقسيم الحالات المتميزة إلى اختبارات مركزة خاصة بها.
يقوم الاختبار بالتحقق من السلوك من خلال مقارنة التمثيل النصي للكائن (toString()، أو JSON.stringify()، أو HTML المصيّر) بسلسلة نصية حرفية متوقعة، مما يربط الاختبار بتنسيقات عرضية بدلاً من القيم المهمة بالفعل.
تقوم عملية تهيئة مشتركة ببناء تجهيز واحد ضخم وشامل يغطي احتياجات كل الاختبارات، ولكن كل اختبار فردي يشغل جزءاً صغيراً جداً منه فقط.
اختبار تعيش مدخلاته أو نتائجه المتوقعة في مورد خارجي — ملف، أو بذرة قاعدة بيانات (seed)، أو تجهيز مشترك — بحيث لا يمكنك فهم الاختبار أو الثقة به بمجرد قراءته بمفرده.
التفاؤل بالموارد هو حين يفترض اختبار ما أن مورداً خارجياً (ملف، مجلد، جدول قاعدة بيانات، متغير بيئة، أو نقطة وصول شبكية) موجود بالفعل وفي حالة معروفة دون توفيره والتحقق منه، مما يجعل الاختبار يجتاز أو يفشل بشكل غير حتمي.
يقوم فئة الاختبار بتهيئة حقول التجهيز (fixture fields) الخاصة به في الباني (constructor) بدلاً من خطاف التهيئة المخصص لإطار العمل (setUp / @BeforeEach / TestInitialize)، مما يؤدي إلى تجاوز دورة حياة الاختبار.
الاختبار الغامض هو اختبار لا يمكن للقارئ أن يحدد من خلال طريقة الاختبار بمفردها ما هو السيناريو المجهز، وما هو السلوك الذي تم تشغيله، وما هي النتيجة المتوقعة — لأن الغرض مدفون في الكثير من التفاصيل، أو قلة السياق، أو منطق مخفي في مكان آخر.
اختبار يستخدم `if`/`switch`/ternaries أو الحلقات أو `try`/`catch` لتحديد ما سيتم تشغيله أو التحقق منه، بحيث يعتمد سلوكه — وما إذا كان يتحقق من أي شيء على الإطلاق — على الفرع الذي يتم تنفيذه في وقت التشغيل.
يحدث تكرار كود الاختبار عندما يتم نسخ ولصق نفس كود الإعداد (setup) أو الإجراء (action) أو التحقق (assertion) عبر اختبارات متعددة، مما يعني أن تغييراً واحداً يفرض تعديلات في أماكن عديدة وتتحول الاختبارات إلى نسخ هشة متطابقة تقريباً.
يكتب الاختبار قيماً رقمية ثابتة غير مفسرة في مدخلاته وتحققاته، مما يخفي معنى الأرقام ومصدرها.
اختبار يستخدم كوداً أكثر بكثير مما يحتاجه لتوضيح السيناريو الخاص به، مما يدفن علاقة السبب والنتيجة التي يتحقق منها تحت أكواد الإعداد الجاهزة (boilerplate)، والبيانات غير ذات الصلة، والتحققات لكل حقل على حدة.
ينجح الاختبار غير المستقر (المتقلب) ويفشل بشكل متقطع على نفس الكود لأن نتيجته تعتمد على التوقيت أو التترتيب أو الحالة المشتركة أو غيرها من العوامل غير الحتمية بدلاً من السلوك قيد الاختبار.
يقوم الاختبار النائم بإيقاف التنفيذ مؤقتاً باستخدام تأخير ثابت مشفر (`Thread.sleep`، أو `setTimeout`، أو `cy.wait(2000)`، أو `page.waitForTimeout`) لانتظار انتهاء العمل غير المتزامن بدلاً من انتظار الشرط الفعلي نفسه.
تحدث حرب تشغيل الاختبارات (Test Run War) عندما تنجح الاختبارات لشخص واحد ولكنها تفشل عشوائياً بمجرد قيام عدة أشخاص أو مهام تكامل مستمر (CI) بتشغيل مجموعة الاختبارات في نفس الوقت، وذلك بسبب تصادم الاختبارات على هيكل اختبار مشترك ومستمر.
ينجح الاختبار أو يفشل اعتماداً على الاختبارات الأخرى التي تم تشغيلها قبله، وذلك لأن الاختبارات تسرب البيانات وتعتمد على حالة مشتركة قابلة للتغيير بدلاً من قيام كل اختبار بإعداد وتفكيك هيكل الاختبار الخاص به بشكل مستقل.
يربط الاختبار عدداً كبيراً جداً من الكائنات الوهمية (mock objects) والتفاعلات البديلة (stubbed interactions) لدرجة أن تجهيز المحاكاة يقزم التحقق الفعلي، مما يجعل الاختبار ينتهي باختبار المحاكاة بدلاً من السلوك الحقيقي.
يقوم الاختبار بالتحقق من كيفية عمل الكود داخلياً — مثل الحقول الخاصة، أو استدعاءات الدوال الداخلية، أو بنية DOM، أو فئات CSS — بدلاً من التحقق من السلوك الملاحظ الذي يعتمد عليه المستخدم الفعلي.
يتحقق الاختبار المحدد بشكل مفرط من تفاصيل تفوق بكثير ما يتطلبه السلوك قيد الاختبار — مثل تثبيت نصوص مخرجات دقيقة، أو هياكل كائنات كاملة، أو ترتيب المجموعات، أو كل استدعاء للمتعاونين الداخليين — مما يجعله ينكسر كلما تغير تفصيل تنفيذي غير ذي صلة.
الاختبار الفارغ هو طريقة اختبار تحتوي على جسم خالٍ من أي عبارات قابلة للتنفيذ، وبالتالي ينجح دائماً دون التحقق من أي شيء.
اختبار تم رفعه إلى كود المشروع ولكنه لا يعمل أبداً لأنه تم تخطيه، أو تعطيله، أو تحويله إلى تعليق، مما يعطي مظهراً كاذباً بالتغطية دون التحقق من أي شيء فعلياً.
يحتوي كود الإنتاج على منطق أو فروع أو أعضاء موجودة فقط لدعم عملية الاختبار، مما يؤدي إلى عدم وضوح الحدود بين ما يتم تسليمه فعلياً للعملاء وما يتم اختباره فقط.
يحتوي كود الإنتاج على طرق، أو وصولات للحالة (accessors)، أو شقوق (seams) توجد فقط لتستخدمها الاختبارات، مما يلوث واجهة برمجة التطبيقات (API) الحقيقية ويدعو إلى اختبارات تتحقق من التفاصيل الداخلية بدلاً من السلوك.
كود الإنتاج الذي يجبر تصميمه (الاقتران الوثيق، أو التبعيات المخفية، أو الحالة العامة، أو الإدخال/الإخراج غير الحتمي، أو الواجهات غير المتزامنة فقط) الاختبارات على القيام بالتواءات محرجة، أو يجعل اختبار الوحدة بشكل معزول أمراً مستحيلاً تماماً.