Einleitung: "Stummer" println! – der bekannte Schmerz jedes Rust-Anfängers
\n\nSie schreiben Ihren ersten Unit-Test in Rust. Alles logisch: Sie erstellen eine Struktur, implementieren eine Methode und dann – der feierliche Moment der Überprüfung. Sie fügen println! in den Test ein, um sicherzustellen, dass die Daten korrekt sind. Sie führen cargo test aus... Und sehen nur eine trockene Zeile: "test result: ok. 1 passed; 0 failed". Kein "Hello, world!", keine Zwischenwerte. Ihr println! ist einfach verschwunden.
Kommt Ihnen bekannt vor? Willkommen im Club. Diese Frage auf Stack Overflow hat tausende Aufrufe gesammelt und ist ein echter Stolperstein für alle, die Testen in Rust lernen. In diesem Artikel sagen wir nicht einfach "verwende --nocapture". Wir analysieren, warum das passiert, wie die Ausgabeerfassung (capture) im Test-Runner funktioniert und wie man Code richtig debuggt, ohne die Prinzipien des Modultests zu verletzen.
Warum "unterdrückt" Rust println! in Tests?
\n\nEs liegt weder an einem Bug noch an böser Absicht der Sprachentwickler. Es ist eine bewusste architektonische Entscheidung. Wenn Sie cargo test ausführen, macht Rust Tests sauber und vorhersagbar. Stellen Sie sich vor, Sie haben 500 Tests und jeder gibt 10 Debug-Zeilen in stdout aus. Ihr Terminal wird zu einem Chaos, und vor allem können Sie echte Fehler nicht von Debug-Müll unterscheiden.
Standardmäßig fängt der Test-Runner (die Bibliothek test) die gesamte Standardausgabe (std::io::stdout()) und die Standardfehlerausgabe (std::io::stderr()) ab. Dies geschieht auf der Ebene der System-Deskriptoren. println! schreibt genau in stdout, daher landet seine Ausgabe in der "Black Box" des Runners. Die Ausgabe wird nur angezeigt, wenn der Test fehlschlägt – damit Sie sehen können, was schiefgelaufen ist.
Wie sieht das im Code aus?
\n\nSchauen wir uns ein einfaches Beispiel an, das das Problem reproduziert:
\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!("Ich werde ausgeführt, aber man sieht mich nicht!");\n let result = add_two(2);\n println!("Ergebnis: {}", result);\n assert_eq!(result, 4);\n }\n}\n\nAusführen:
\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\nWie Sie sehen, ist keine der println!-Nachrichten erschienen. Der Test war erfolgreich, und der Runner entschied, dass es nichts anzuzeigen gibt.
Wie "weckt" man println! auf? Methoden und Fallstricke
\n\nEs gibt drei Hauptansätze, und jeder ist für seine Situation geeignet.
\n\nMethode 1: Das --nocapture-Flag (am einfachsten und beliebtesten)
\n\nDas ist das, was Sie in der ersten Antwort auf Stack Overflow finden. Das Flag --nocapture deaktiviert die Ausgabeerfassung für alle Tests im aktuellen Durchlauf.
$ cargo test -- --nocapture\n\nBeachten Sie den doppelten Bindestrich --. Er trennt die Argumente für cargo und für das Test-Binary selbst. Alles nach -- wird an den Test-Runner übergeben.
Jetzt sieht die Ausgabe so aus:
\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\nIch werde ausgeführt, aber man sieht mich nicht!\nErgebnis: 4\ntest tests::test_add_two ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered