مقدمة: println! "الصامت" — الألم المألوف لكل مبتدئ في Rust
\n\nأنت تكتب أول اختبار وحدة في Rust. كل شيء منطقي: تقوم بإنشاء هيكل، وتنفيذ دالة، ثم تأتي لحظة التحقق المنتظرة. تقوم بإدراج println! في الاختبار للتأكد من صحة البيانات. تقوم بتشغيل cargo test... وترى فقط سطرًا جافًا: "test result: ok. 1 passed; 0 failed". لا "Hello, world!"، ولا أي قيم وسيطة. لقد اختفى println! الخاص بك ببساطة.
هل هذا مألوف؟ مرحبًا بك في النادي. هذا السؤال على Stack Overflow حصد آلاف المشاهدات، وهو حجر عثرة حقيقي لكل من يدرس الاختبار في Rust. في هذه المقالة، لن نقول فقط "استخدم --nocapture". سنقوم بتحليل لماذا يحدث هذا، وكيف يعمل التقاط الإخراج (capture) في مشغل الاختبارات، وكيفية تصحيح الأخطاء بشكل صحيح دون انتهاك مبادئ اختبار الوحدة.
لماذا "يكتم" Rust println! في الاختبارات؟
\n\nالأمر لا يتعلق بخلل أو نية شريرة من مبتكري اللغة. إنه قرار معماري مدروس. عندما تقوم بتشغيل cargo test، يجعل Rust الاختبارات نظيفة وقابلة للتنبؤ. تخيل أن لديك 500 اختبار، وكل منها يخرج 10 أسطر من التصحيح في stdout. سيتحول الطرفية الخاصة بك إلى فوضى، والأهم من ذلك، لن تتمكن من التمييز بين الأخطاء الحقيقية والنفايات التصحيحية.
افتراضيًا، يقوم مشغل الاختبارات (مكتبة test) باعتراض كل الإخراج القياسي (std::io::stdout()) والإخراج القياسي للأخطاء (std::io::stderr()). يفعل ذلك على مستوى واصفات النظام. println! يكتب تحديدًا في stdout، لذلك يقع إخراجه في "الصندوق الأسود" للمشغل. يظهر الإخراج فقط إذا فشل الاختبار — حتى تتمكن من رؤية ما حدث بشكل خاطئ.
كيف يبدو هذا في الكود؟
\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!. نجح الاختبار، وقرر المشغل أنه لا يوجد شيء ليظهره لك.
كيف "نوقظ" println!؟ الطرق والمزالق
\n\nهناك ثلاثة طرق رئيسية، وكل منها مناسب لموقفه الخاص.
\n\nالطريقة 1: العلم --nocapture (الأبسط والأكثر شيوعًا)
\n\nهذا ما ستجده في أول إجابة على Stack Overflow. العلم --nocapture يعطل اعتراض الإخراج لـ جميع الاختبارات في التشغيل الحالي.
$ cargo test -- --nocapture\n\nلاحظ الشرطتين المزدوجتين --. هما يفصلان الوسائط الخاصة بـ cargo عن الوسائط الخاصة بملف الاختبار الثنائي نفسه. كل ما بعد -- يتم تمريره إلى مشغل الاختبارات.
الآن سيكون الإخراج كالتالي:
\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