كيف تكتب اختبارات برمجية تكشف الأعطال مبكرًا؟
عالم البرمجة
قد يمر التعديل على جهازك بلا مشكلة، ينجح البناء، وتبدو الشاشة سليمة، ثم يكتشف أول مستخدم
أن زر الدفع لا يكمل العملية أو أن تسجيل الدخول يفشل في حالة لم تمر عليك أثناء التطوير.
هنا لا تكون المشكلة أن المطور لم يجرب الكود، بل أن التجربة لم تكن مصممة لتكشف ما يمكن أن ينكسر.
الاختبارات البرمجية لا تجعل التطبيق خاليًا من الأخطاء، ولا تستطيع إثبات أن كل مسار سيعمل دائمًا.
قيمتها الحقيقية أنها تحول بعض المخاطر المعروفة إلى أسئلة قابلة للفحص قبل النشر:
هل ما زالت القاعدة تعمل؟ ماذا يحدث عند فشل خدمة خارجية؟ هل التعديل الجديد كسر سلوكًا قديمًا؟
ولهذا لا تبدأ شبكة الاختبارات الجيدة من سؤال: كم اختبارًا أكتب؟ ابدأ من سؤال أدق: ما السلوك
الذي لا أريد أن يكتشف المستخدم تعطله قبلي؟
هذا التحول يغير طريقة الاختبار كلها.
بدل ملاحقة كل دالة وكل سطر، ترتب المخاطر، وتختار الطبقة المناسبة، وتكتب حالات نجاح وفشل يمكن إعادة تشغيلها بثبات.
عندها تصبح الاختبارات أداة لاتخاذ قرار أفضل قبل الدمج والنشر، لا رقمًا جميلًا في تقرير التغطية.
اختبر السلوك المهم
ليست أجزاء التطبيق متساوية في أثرها.
فشل لون زر ثانوي لا يشبه إنشاء طلب مرتين، وفشل رسالة مساعدة لا يشبه فقد ملف أو قبول عملية دفع على أنها ناجحة وهي مرفوضة.
لذلك ابدأ بالمسارات التي يترتب على تعطلها أثر واضح على المستخدم أو البيانات أو الخدمة.
اكتب السلوك بصيغة يمكن ملاحظتها.
في متجر مثلًا: عند نجاح الدفع يُنشأ الطلب مرة واحدة.
عند رفض البوابة لا يتحول الطلب إلى مدفوع.
عند تكرار الطلب بالمفتاح نفسه لا تنشأ معاملة ثانية إذا كان تصميم النظام يمنع ذلك.
هذه الصياغة أقوى من قولك: سأختبر دالة الدفع.
الدالة قد تتغير، وقد تنقسم إلى أكثر من جزء، لكن القاعدة التي يعتمد عليها المنتج يجب أن تبقى واضحة.
الاختبار المفيد يثبت عقدًا سلوكيًا، لا تفاصيل داخلية بلا ضرورة.
إذا كان الفحص ينهار كلما أعدت تسمية دالة خاصة أو غيرت ترتيبًا داخليًا لا يؤثر في النتيجة، فقد صار مرتبطًا بالتنفيذ أكثر مما ينبغي.
هذا لا يعني تجاهل التفاصيل الداخلية دائمًا.
بعض المنطق الصغير يستحق اختبارًا مباشرًا لأنه معقد أو حساس.
الفكرة أن تعرف لماذا تختبره، وما الخطأ الذي تريد أن يكشفه الاختبار إذا ظهر.
اختر طبقة الاختبار
اختبار الوحدة يجيب عادة عن سؤال صغير ومحدد: هل هذا الجزء من المنطق يعطي النتيجة الصحيحة
عند مدخلات معروفة؟ ميزته أنه سريع، معزول نسبيًا، وسهل التشخيص عندما يفشل.
وهو مناسب لقواعد الحساب، والتحقق، والتحويل، والقرارات المنطقية التي لا تحتاج إلى تشغيل النظام كاملًا.
لكن نجاح وحدات منفصلة لا يثبت أن المكونات تعمل معًا.
قد تكون كل دالة صحيحة بينما يفشل الاتصال بقاعدة البيانات، أو يختلف شكل البيانات بين خدمتين،
أو لا تعمل المصادقة كما يتوقع المسار الحقيقي.
هنا تظهر قيمة اختبار التكامل.
أما الاختبار من طرف إلى طرف فيفحص رحلة أوسع تشبه استخدام النظام، مثل تسجيل الدخول ثم إضافة منتج ثم إتمام طلب في بيئة مخصصة للاختبار.
هذا يمنح ثقة في المسار المركب، لكنه أبطأ وأكثر تكلفة في الصيانة عادة، لذلك لا يحتاج كل تفصيل
إلى هذا المستوى.
القاعدة العملية: استخدم أصغر طبقة تستطيع كشف الخطر الذي يهمك بثقة مناسبة.
إذا كان الخطأ داخل قاعدة حسابية، لا تشغل متصفحًا كاملًا لإثباتها.
وإذا كان الخطر في تكامل قاعدة البيانات، فلا تستبدلها كلها بمحاكاة ثم تعتبر التكامل مختبرًا.
اختبر الفشل المتوقع
المسار السعيد ضروري، لكنه لا يكفي.
المستخدم لا يقدم دائمًا البيانات المثالية، والشبكة لا تستجيب دائمًا في الوقت المناسب، والخدمات الخارجية قد ترفض أو تتأخر، وقد تتكرر الطلبات بسبب إعادة المحاولة أو ضعف الاتصال.
لهذا أضف حالات فشل مرتبطة بطبيعة الميزة.
إذا كنت تختبر إدخالًا، جرّب القيم الفارغة والحدود والأطوال غير المتوقعة عندما تكون ذات معنى.
وإذا كنت تختبر عملية مالية، افحص الرفض والتكرار وانتهاء المهلة والحالة التي تنجح فيها خطوة وتتعطل أخرى.
لا تحول ذلك إلى مسابقة لاختراع أغرب مدخل ممكن.
الحالة الحدية تستحق اختبارًا عندما يكون لها احتمال معقول أو أثر مهم أو تاريخ سابق في النظام.
وعندما يظهر عطل حقيقي في الإنتاج، حاول قبل إصلاحه أن تعيد إنتاجه باختبار إن أمكن.
بعد ذلك أصلح السبب واترك الاختبار ضمن المجموعة.
بهذا يتحول العطل من حادثة عابرة إلى معرفة محفوظة تمنع التراجع نفسه من المرور بسهولة مرة أخرى.
وإذا كان النظام يعرض رسالة للمستخدم عند الفشل، فاختبر السلوك المفيد لا مجرد وقوع الاستثناء.
قد يكون المطلوب ألا تضيع البيانات، أو ألا تتكرر العملية، أو أن تظهر حالة مفهومة تسمح للمستخدم بالمحاولة لاحقًا.
وهناك فحص آخر يستحق الانتباه بعد كل تعديل كبير: هل بقيت الرسالة التي يفهمها المستخدم مطابقة للحالة الحقيقية للنظام؟ قد يفشل الطلب تقنيًا بينما تعرض الواجهة نجاحًا، أو ينجح الحفظ ثم يبقى الزر
في حالة انتظار، أو يعاد إرسال العملية لأن العميل لم يتلق التأكيد في الوقت المناسب.
هذه الأعطال لا تكشفها دائمًا اختبارات المنطق الصغيرة وحدها.
اختيار موضع الاختبار يجب أن يتبع مكان الخطر نفسه، لا الاسم الشائع لنوع الاختبار.
وكلما كان الأثر موزعًا بين أكثر من مكون، احتجت فحصًا يراقب النتيجة عند الحد الذي يمكن أن يظهر
فيه التناقض بوضوح فعلًا.
لا تثق بالمحاكاة وحدها
عندما يعتمد الكود على بوابة دفع أو خدمة رسائل أو مزود خارجي، فإن الاتصال بالخدمة الحقيقية
في كل اختبار صغير يجعل التنفيذ أبطأ وأكثر تعرضًا لأسباب فشل لا تتعلق بالمنطق الذي تختبره.
هنا تفيد بدائل الاختبار والمحاكاة.
تستطيع أن تجعل الخدمة تعيد نجاحًا أو رفضًا أو انتهاء مهلة، ثم تفحص كيف يتصرف كودك.
هذا مناسب عندما يكون هدفك اختبار القرار المحلي لا اختبار المزود نفسه.
لكن هناك فخ مهم: المحاكاة تتصرف بالطريقة التي أنت برمجتها عليها.
إذا كان افتراضك عن الخدمة الخارجية خاطئًا، فقد تنجح كل الفحوص بينما يفشل التكامل الحقيقي.
لذلك لا تجعل البديل شاهدك الوحيد على صحة التكامل.
اقرأ ايضا : كيف تختار بنية مشروع تتحمل نمو التطبيق؟
احتفظ باختبارات مناسبة تتحقق من الحدود الفعلية بين المكونات أو من العقد الذي تتبادله الخدمات، بحسب بنية مشروعك وإمكاناته.
الأمر نفسه ينطبق على قواعد البيانات.
استبدال قاعدة الإنتاج ببديل قد يكون مفيدًا لبعض اختبارات المنطق، لكنه لا يثبت أن الاستعلامات والقيود والسلوك الخاص بمحرك البيانات الفعلي تعمل كما تتوقع.
حدّد بوضوح ما الذي عزلته، وما الذي لم تختبره بعد.
عالج الاختبار المتذبذب
أسوأ ما يمكن أن يحدث لمجموعة الاختبارات ليس الفشل المتكرر فقط، بل أن يصبح اللون الأحمر غير جدير بالثقة.
الاختبار الذي ينجح ويفشل من دون تغيير ذي صلة يدرب الفريق مع الوقت على إعادة التشغيل بدل التحقيق.
قد يأتي التذبذب من انتظار زمني ثابت، أو بيانات مشتركة، أو اعتماد على ترتيب الاختبارات، أو عشوائية
غير قابلة للإعادة، أو وقت النظام، أو خدمة خارجية غير مستقرة.
لذلك زيادة مدة الانتظار ليست علاجًا عامًا.
في اختبارات الواجهات غير المتزامنة، انتظر الحالة التي تستطيع ملاحظتها بدل النوم لعدد ثابت من الثواني متى كانت الأداة تتيح ذلك.
وإذا كانت البيانات سببًا، امنح كل اختبار حالة معروفة ومستقلة قدر الإمكان.
إذا دخل الزمن في المنطق، استخدم وسيلة للتحكم فيه داخل الاختبار متى سمحت البنية.
وإذا احتجت إلى العشوائية، احتفظ بما يسمح بإعادة الحالة التي فشلت.
إعادة تشغيل الاختبار الفاشل قد تساعدك على كشف أنه متذبذب أو جمع معلومات إضافية، لكنها لا تصلح السبب.
الهدف أن يصبح الفشل إشارة جدية تستحق التحقيق، لا أن تثبت أن كل فشل يعني حتمًا وجود عيب
في كود الإنتاج.
لا تطارد نسبة التغطية
تغطية الكود مفيدة لأنها تكشف أجزاء لم تمر عليها الاختبارات، لكنها لا تعرف إن كان التحقق الذي كتبته قويًا.
يمكن لاختبار أن ينفذ سطرًا ويرفع النسبة من دون أن يثبت النتيجة المهمة.
لذلك لا تحول رقم التغطية إلى تعريف للجودة.
استخدمه كإشارة تسألك: لماذا هذه المنطقة غير مختبرة؟ وهل تمثل خطرًا يستحق الاختبار؟
ثم عد إلى السلوك بدل مطاردة الرقم وحده.
جرّب سؤالًا بسيطًا على الاختبارات المهمة: لو كسرت القاعدة التي يفترض أن يحميها هذا الاختبار عمدًا،
هل سيفشل؟ إذا بقي أخضر، فوجوده قد يمنحك ثقة أكبر مما يستحق.
ينطبق المنطق نفسه على عدد الاختبارات.
ألف فحص ضعيف لا يتفوق تلقائيًا على مجموعة أصغر تستهدف قواعد ومسارات حساسة بوضوح.
الصيانة جزء من الجودة أيضًا.
اسم الاختبار يجب أن يشرح ما الذي انكسر، وإعداده لا ينبغي أن يحتاج إلى متاهة من الخطوات غير الضرورية.
كلما كان سبب الفشل أوضح، انخفض الوقت بين رؤية التنبيه وفهم المشكلة.
اربط الفحص بالدمج
قيمة الاختبار تنخفض إذا بقي تشغيله معتمدًا على ذاكرة المطور.
التكامل المستمر يسمح بتشغيل الفحوص آليًا عند تغييرات محددة، مثل دفع الكود أو فتح طلب
دمج، ثم إظهار النتيجة قبل انتقال التغيير إلى مرحلة لاحقة.
لكن وجود نظام تكامل مستمر لا يعني وحده أن الكود الفاشل يستحيل دمجه.
إذا أردت أن تكون فحوص معينة بوابة إلزامية، فيجب ضبط قواعد المستودع أو الحماية والفحوص المطلوبة وفق المنصة التي تستخدمها.
رتب الاختبارات بحسب سرعة التغذية الراجعة.
شغّل الفحوص السريعة مبكرًا، واترك الاختبارات الأثقل للمرحلة التي تناسب تكلفتها.
الهدف ألا ينتظر المطور وقتًا طويلًا ليعرف أنه كسر قاعدة بسيطة، وفي الوقت نفسه لا تتخلى عن اختبارات أوسع يحتاجها المشروع.
وعندما يطول زمن المجموعة، لا تعالج المشكلة بحذف الاختبارات المهمة عشوائيًا.
ابحث عن السبب: إعداد يتكرر، اعتماد خارجي، بيانات بطيئة، اختبار واسع يمكن تقسيمه، أو فحوص متشابهة لا تضيف معلومة جديدة.
شبكة الاختبار الجيدة لا تكون الأسرع بأي ثمن، بل تعطي المعلومة المناسبة في الوقت المناسب.
افصل الاختبار عن الإنتاج
لا تجعل راحة الاختبار سببًا لفتح باب نحو بيانات حقيقية أو أسرار إنتاجية.
بيئات التطوير والاختبار والإنتاج تحتاج إلى فصل واضح في الحسابات والمفاتيح والصلاحيات والموارد
بقدر ما تسمح به بنية النظام.
ابدأ ببيانات تركيبية عندما تستطيع.
أنشئ بيانات تمثل البنية والحالات المهمة من دون أن تكون سجلات مستخدمين حقيقيين.
ولا تجعل إزالة الاسم وحدها مبررًا لاعتبار أي نسخة من بيانات الإنتاج آمنة؛ التعامل مع البيانات الواقعية يحتاج ضوابط الخصوصية والأمن والسياسات المناسبة لسياق المشروع.
صمم بيانات الاختبار لتكون مفيدة أيضًا.
أضف الحالات الفارغة والحدود والتكرار والعلاقات المفقودة عندما تكون ممكنة في النظام.
البيانات المثالية فقط قد تجعل الاختبار أخضر لأنها لا تمثل الأماكن التي ينكسر فيها السلوك.
وافصل الخدمات التي قد تسبب أثرًا حقيقيًا.
اختبار إرسال رسالة يجب ألا يتحول بالخطأ إلى رسالة لعميل فعلي، واختبار الدفع لا ينبغي أن يستخدم اعتمادًا إنتاجيًا لمجرد سهولة الإعداد.
هذا الفصل لا يجعل الاختبارات آمنة تلقائيًا، لكنه يقلل مساحة الخطأ ويجعل حدود البيئة أوضح للفريق.
ابنِ خريطة العطل
قبل كتابة الاختبار، خذ المسار الذي تريد حمايته ومرره عبر «خريطة العطل قبل المستخدم».
هي ليست معيارًا صناعيًا، بل طريقة عملية تساعدك على ربط كل فحص بسبب واضح.
ابدأ بالسلوك: ما النتيجة التي يعتمد عليها المستخدم؟ ثم الخطر: ما الفشل المعقول الذي يستحق
أن أعرفه قبل النشر؟ بعد ذلك اختر الطبقة: هل يكشفه اختبار وحدة، أم يحتاج تكاملًا، أم لا يظهر إلا في مسار أوسع؟
حدد الدليل بعد ذلك: ما الحالة أو المخرج الذي سيثبت أن السلوك صحيح؟ ثم اسأل عن مصدر عدم الثبات:
هل يعتمد الاختبار على وقت، أو شبكة، أو بيانات مشتركة، أو عشوائية، أو خدمة خارجية؟ ما الذي تستطيع ضبطه كي يعاد الاختبار تحت شروط معروفة؟
أخيرًا اسأل عن فجوة الاختبار: ماذا لا يثبت هذا الفحص؟ إذا استخدمت محاكاة، فقد يبقى التكامل الحقيقي غير مختبر.
اقرأ ايضا : لماذا تظهر الأخطاء بعد تحديث مكتبة رغم أن مشروعك كان مستقرًا؟
وإذا اختبرت وحدة صغيرة، فقد تبقى رحلة المستخدم خارج النطاق.
معرفة هذه الفجوة تمنع الثقة الزائدة.
طبّق الخريطة اليوم على مسار واحد عالي الأثر.
اكتب حالة نجاح، وحالة فشل معقولة، واختر أصغر طبقة تكشفهما، ثم اجعل الفحص يعمل ضمن دورة الدمج المناسبة.
الاختبار الجيد لا يعدك بأن المستخدم لن يرى عطلًا أبدًا.
هو يقلل احتمال أن يكون المستخدم أول من يخبرك بعطل كان بإمكانك تحويله مسبقًا إلى سؤال واضح وقابل للفحص.
