مقدمة: لماذا الاختبارات في Rust ليست مجرد موضة، بل ضرورة
\n\nRust هي لغة تشتهر بأمان الذاكرة والأداء العالي. ومع ذلك، حتى أكثر المترجمات صرامة لا تضمن صحة منطق الأعمال. وهنا يأتي دور الاختبارات. في Rust، يقوم نظام الأنواع والملكية بالفعل باستبعاد فئة كاملة من الأخطاء في مرحلة الترجمة، لكن اختبارات الوحدة والتكامل والاختبارات الشاملة (e2e) تسمح بالتأكد من أن الكود الخاص بك يفعل بالضبط ما قصدته.
\n\nفي هذه المقالة، سنقوم بتحليل ثلاثة مستويات من الاختبارات في Rust: من اختبارات الوحدة المعزولة إلى سيناريوهات e2e الكاملة. ستتعلم كيفية كتابة اختبارات فعالة، وما هي الأدوات التي يجب استخدامها، وكيفية تنظيم البنية التحتية للاختبار. ستكون المادة مفيدة لكل من المبتدئين الذين بدأوا للتو في تعلم Rust، والمطورين ذوي الخبرة الذين يرغبون في تنظيم معرفتهم.
\n\n1. اختبارات الوحدة: الأساس
\n\nاختبارات الوحدة (unit tests) هي أسرع وأكثر مستوى اختبار عزلة. إنها تختبر دوالًا فردية، أو طرقًا، أو وحدات صغيرة بمعزل عن التبعيات الخارجية. في Rust، من المعتاد وضع اختبارات الوحدة في نفس الملف الذي يحتوي على الكود المختبر، داخل وحدة tests.
1.1. الهيكل والسمات
\n\nلإنشاء اختبار وحدة، يتم استخدام السمة #[test]. الدالة المميزة بها لا تأخذ وسائط ولا تُرجع شيئًا (أو تُرجع Result). إذا حدث ذعر (panic) داخل الدالة، يعتبر الاختبار فاشلاً.
// src/lib.rs\npub fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_add() {\n assert_eq!(add(2, 2), 4);\n }\n\n #[test]\n fn test_add_negative() {\n assert_eq!(add(-1, -1), -2);\n }\n}\n\nلاحظ #[cfg(test)]. تضمن هذه السمة أن وحدة الاختبارات سيتم ترجمتها فقط عند تشغيل الاختبارات (cargo test). لن يتم تضمينها في بناء الإنتاج.
1.2. وحدات الماكرو للتحقق
\n\nتوفر Rust العديد من وحدات الماكرو للتحقق من الادعاءات:
\n\n- \n
assert!(expr)— تتحقق من أن التعبير صحيح. \n assert_eq!(left, right)— تتحقق من المساواة (تتطلبPartialEqوDebug). \n assert_ne!(left, right)— تتحقق من عدم المساواة. \n panic!("message")— تسبب ذعرًا إجباريًا (تُستخدم لاختبار الأخطاء). \n
1.3. اختبار الذعر باستخدام should_panic
\n\nإذا كانت الدالة الخاصة بك يجب أن تسبب ذعرًا عند إدخال بيانات غير صحيحة، استخدم السمة #[should_panic]:
pub fn divide(a: f64, b: f64) -> f64 {\n if b == 0.0 {\n panic!("Division by zero");\n }\n a / b\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n #[should_panic(expected = "Division by zero")]\n fn test_divide_by_zero() {\n divide(10.0, 0.0);\n }\n}\n\nالمعامل expected اختياري، لكنه يجعل الاختبار أكثر دقة: يجب أن يحتوي الذعر على السلسلة المحددة.
2. اختبارات التكامل: التحقق من تفاعل الوحدات
\n\nتتحقق اختبارات التكامل من كيفية عمل الأجزاء المختلفة من تطبيقك معًا. على عكس اختبارات الوحدة، فهي موجودة في دليل منفصل tests/ على مستوى جذر المشروع (بجانب src/). يتم ترجمة كل ملف في هذا المجلد كصندوق (crate) مكتبة منفصل.
2.1. إنشاء اختبار تكامل
\n\nأنشئ ملفًا tests/integration_test.rs:
// tests/integration_test.rs\nuse my_crate::add; // نفترض أن الصندوق الخاص بك يسمى my_crate\n\n#[test]\nfn test_add_integration() {\n let result = add(5, 10);\n assert_eq!(result, 15);\n}\n\nلتشغيل اختبارات التكامل فقط