الذعر ليس حكمًا: لماذا يجب اختبار حالات الطوارئ في Rust؟
\n\nتخيل: أنت تكتب مكتبة بلغة Rust للتعامل مع الأموال. الدالة divide_money(amount: u64, parts: u64) يجب أن تسبب ذعرًا إذا كانت parts تساوي صفرًا — لأنه لا يمكن القسمة على صفر، ولم ترغب في إرجاع Option أو Result لأسباب معمارية. كيف تتأكد من أن هذا الذعر يحدث بالفعل؟ أو أنك تطور محللًا يجب أن يتوقف بشكل طارئ عند تمرير بيانات غير صحيحة. بدون اختبارات الذعر، فإنك تخاطر بوجود أخطاء "صامتة" ستظهر فقط في بيئة الإنتاج.
في Rust، اختبار الذعر ليس مجرد "خيار لطيف"، بل أداة مهمة لإنشاء كود موثوق. في هذه المقالة، سنشرح كيفية كتابة اختبارات الوحدة التي تتحقق من أن الكود الخاص بك يسبب ذعرًا بالفعل في المواقف المناسبة. ستتعرف على وحدات الماكرو #[should_panic]، ودقائقها، وبدائلها مثل std::panic::catch_unwind، وأفضل الممارسات. هيا بنا!
النهج الكلاسيكي: وحدة الماكرو #[should_panic]
\n\nأبسط وأشهر طريقة للتحقق من الذعر في Rust هي استخدام السمة #[should_panic]. توضع فوق دالة الاختبار وتخبر المترجم: "يعتبر هذا الاختبار ناجحًا فقط إذا حدث ذعر بداخله".
مثال أساسي
\n\n// lib.rs أو tests/unit_tests.rs\nfn divide(a: i32, b: i32) -> i32 {\n if b == 0 {\n panic!("القسمة على صفر!");\n }\n a / b\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n #[should_panic]\n fn test_divide_by_zero() {\n divide(10, 0);\n }\n}\n\nقم بتشغيل الاختبارات باستخدام الأمر cargo test — وسترى نتيجة خضراء. إذا لم يحدث ذعر (على سبيل المثال، إذا أزلت التحقق من الصفر)، سيفشل الاختبار مع رسالة: "test test_divide_by_zero ... FAILED (expected to panic)".
التحقق من رسالة الذعر الدقيقة
\n\nأحيانًا تحتاج إلى التأكد ليس فقط من حدوث الذعر، ولكن من رسالته المحددة. يساعد هذا في تجنب النتائج الإيجابية الخاطئة، عندما يحدث الذعر بسبب خطأ آخر. استخدم المعامل الإضافي expected:
#[test]\n#[should_panic(expected = "القسمة على صفر!")]\nfn test_divide_by_zero_with_message() {\n divide(10, 0);\n}\n\n// هذا الاختبار لن ينجح — الرسالة مختلفة\n#[test]\n#[should_panic(expected = "القسمة على صفر!")]\nfn test_other_panic() {\n panic!("خطأ مختلف تمامًا");\n}\n\nهام: expected يتحقق من أن رسالة الذعر تحتوي على السلسلة المحددة (وليس أنها مساوية لها تمامًا). هذا مفيد للرسائل الطويلة.
النهج المتقدم: std::panic::catch_unwind
\n\nأحيانًا لا يكون #[should_panic] كافيًا. على سبيل المثال، إذا كنت تريد التحقق من أن الذعر حدث في مكان محدد وليس في مكان آخر، أو إذا كنت بحاجة لاختبار كود غير متزامن. في مثل هذه الحالات، توجد الدالة std::panic::catch_unwind — وهي دالة تعترض الذعر وتعيده كـ Result.
كيف تعمل
\n\nuse std::panic;\n\nfn risky_function(should_panic: bool) {\n if should_panic {\n panic!("طارئ!");\n }\n}\n\n#[test]\nfn test_catch_panic() {\n let result = panic::catch_unwind(|| {\n risky_function(true);\n });\n assert!(result.is_err()); // تحقق من حدوث ذعر\n}\n\n#[test]\nfn test_no_panic() {\n let result = panic::catch_unwind(|| {\n risky_function(false);\n });\n assert!(result.is_ok()); // لم يحدث ذعر\n}\n\nالتحقق من نوع الذعر
\n\nباستخدام catch_unwind يمكنك حتى التحقق من أن الذعر يحتوي على قيمة محددة (وليس مجرد سلسلة نصية):
use std::panic;\nuse std::any::Any;\n\nfn panic_with_number() {\n panic!(42); // نعم، هذا ممكن — ذعر برقم!\n}\n\n#[test]\nfn test_panic_with_number() {\n let resu