لماذا يصمت println! في اختبارات Rust؟ تحليل كامل

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: println! "الصامت" — الألم المألوف لكل مبتدئ في Rust

\n\n

أنت تكتب أول اختبار وحدة في Rust. كل شيء منطقي: تقوم بإنشاء هيكل، وتنفيذ دالة، ثم تأتي لحظة التحقق المنتظرة. تقوم بإدراج println! في الاختبار للتأكد من صحة البيانات. تقوم بتشغيل cargo test... وترى فقط سطرًا جافًا: "test result: ok. 1 passed; 0 failed". لا "Hello, world!"، ولا أي قيم وسيطة. لقد اختفى println! الخاص بك ببساطة.

\n\n

هل هذا مألوف؟ مرحبًا بك في النادي. هذا السؤال على Stack Overflow حصد آلاف المشاهدات، وهو حجر عثرة حقيقي لكل من يدرس الاختبار في Rust. في هذه المقالة، لن نقول فقط "استخدم --nocapture". سنقوم بتحليل لماذا يحدث هذا، وكيف يعمل التقاط الإخراج (capture) في مشغل الاختبارات، وكيفية تصحيح الأخطاء بشكل صحيح دون انتهاك مبادئ اختبار الوحدة.

\n\n

لماذا "يكتم" Rust println! في الاختبارات؟

\n\n

الأمر لا يتعلق بخلل أو نية شريرة من مبتكري اللغة. إنه قرار معماري مدروس. عندما تقوم بتشغيل cargo test، يجعل Rust الاختبارات نظيفة وقابلة للتنبؤ. تخيل أن لديك 500 اختبار، وكل منها يخرج 10 أسطر من التصحيح في stdout. سيتحول الطرفية الخاصة بك إلى فوضى، والأهم من ذلك، لن تتمكن من التمييز بين الأخطاء الحقيقية والنفايات التصحيحية.

\n\n

افتراضيًا، يقوم مشغل الاختبارات (مكتبة test) باعتراض كل الإخراج القياسي (std::io::stdout()) والإخراج القياسي للأخطاء (std::io::stderr()). يفعل ذلك على مستوى واصفات النظام. println! يكتب تحديدًا في stdout، لذلك يقع إخراجه في "الصندوق الأسود" للمشغل. يظهر الإخراج فقط إذا فشل الاختبار — حتى تتمكن من رؤية ما حدث بشكل خاطئ.

\n\n

كيف يبدو هذا في الكود؟

\n\n

لنلقِ نظرة على مثال بسيط يعيد إنتاج المشكلة:

\n\n
// src/lib.rs\npub fn add_two(a: i32) -> i32 {\n    a + 2\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_add_two() {\n        println!("أنا أُنفذ لكن لا يمكن رؤيتي!");\n        let result = add_two(2);\n        println!("النتيجة: {}", result);\n        assert_eq!(result, 4);\n    }\n}
\n\n

نقوم بالتشغيل:

\n\n
$ cargo test\n   Compiling my_project v0.1.0\n    Finished test [unoptimized + debuginfo] target(s) in 0.5s\n     Running unittests src/lib.rs (target/debug/deps/my_project-abc123)\n\nrunning 1 test\ntest tests::test_add_two ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
\n\n

كما ترى، لم تظهر أي من رسائل println!. نجح الاختبار، وقرر المشغل أنه لا يوجد شيء ليظهره لك.

\n\n

كيف "نوقظ" println!؟ الطرق والمزالق

\n\n

هناك ثلاثة طرق رئيسية، وكل منها مناسب لموقفه الخاص.

\n\n

الطريقة 1: العلم --nocapture (الأبسط والأكثر شيوعًا)

\n\n

هذا ما ستجده في أول إجابة على Stack Overflow. العلم --nocapture يعطل اعتراض الإخراج لـ جميع الاختبارات في التشغيل الحالي.

\n\n
$ cargo test -- --nocapture
\n\n

لاحظ الشرطتين المزدوجتين --. هما يفصلان الوسائط الخاصة بـ cargo عن الوسائط الخاصة بملف الاختبار الثنائي نفسه. كل ما بعد -- يتم تمريره إلى مشغل الاختبارات.

\n\n

الآن سيكون الإخراج كالتالي:

\n\n
$ cargo test -- --nocapture\n   Compiling my_project v0.1.0\n    Finished test [unoptimized + debuginfo] target(s) in 0.5s\n     Running unittests src/lib.rs (target/debug/deps/my_project-abc123)\n\nrunning 1 test\nأنا أُنفذ لكن لا يمكن رؤيتي!\nالنتيجة: 4\ntest tests::test_add_two ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered                

المدونات

توصيات الكتب