Introduction : Le "muet" println! — la douleur familière de chaque débutant en Rust
\n\nVous écrivez votre premier test unitaire en Rust. Tout est logique : vous créez une structure, implémentez une méthode, puis — le moment solennel de la vérification. Vous insérez println! dans le test pour vous assurer que les données sont correctes. Vous lancez cargo test... Et vous ne voyez qu'une ligne sèche : "test result: ok. 1 passed; 0 failed". Aucun "Hello, world!", aucune valeur intermédiaire. Votre println! a simplement disparu.
Familiar ? Bienvenue au club. Cette question sur Stack Overflow a rassemblé des milliers de vues, et c'est une véritable pierre d'achoppement pour tous ceux qui étudient le test en Rust. Dans cet article, nous ne dirons pas simplement "utilisez --nocapture". Nous analyserons pourquoi cela se produit, comment fonctionne la capture de sortie (capture) dans l'exécuteur de tests, et comment déboguer correctement le code sans violer les principes des tests unitaires.
Pourquoi Rust "muselle" println! dans les tests ?
\n\nCe n'est ni un bug ni une intention malveillante des créateurs du langage. C'est une décision architecturale délibérée. Lorsque vous lancez cargo test, Rust rend les tests propres et prévisibles. Imaginez que vous ayez 500 tests, et que chacun affiche 10 lignes de débogage dans stdout. Votre terminal deviendrait un fouillis, et surtout, vous ne pourriez pas distinguer les vraies erreurs du bruit de débogage.
Par défaut, l'exécuteur de tests (la bibliothèque test) intercepte toute la sortie standard (std::io::stdout()) et la sortie d'erreur standard (std::io::stderr()). Il le fait au niveau des descripteurs système. println! écrit précisément dans stdout, donc sa sortie tombe dans la "boîte noire" de l'exécuteur. La sortie n'est affichée que si le test échoue — afin que vous puissiez voir ce qui n'a pas fonctionné.
Comment cela se présente-t-il dans le code ?
\n\nRegardons un exemple simple qui reproduit le problème :
\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!("Je m'exécute, mais on ne me voit pas !");\n let result = add_two(2);\n println!("Résultat : {}", result);\n assert_eq!(result, 4);\n }\n}\n\nLançons :
\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\nComme vous pouvez le voir, aucun des messages println! n'est apparu. Le test a réussi, et l'exécuteur a décidé qu'il n'y avait rien à vous montrer.
Comment "réveiller" println! ? Méthodes et pièges
\n\nIl existe trois approches principales, chacune adaptée à sa situation.
\n\nMéthode 1 : Le drapeau --nocapture (le plus simple et le plus populaire)
\n\nC'est ce que vous trouverez dans la première réponse sur Stack Overflow. Le drapeau --nocapture désactive l'interception de la sortie pour tous les tests de l'exécution en cours.
$ cargo test -- --nocapture\n\nNotez le double tiret --. Il sépare les arguments pour cargo de ceux pour le binaire de test lui-même. Tout ce qui se trouve après -- est transmis à l'exécuteur de tests.
Maintenant, la sortie sera :
\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\nJe m'exécute, mais on ne me voit pas !\nRésultat : 4\ntest tests::test_add_two ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered