Introducción: println! "mudo" — el dolor familiar de todo principiante en Rust
\n\nEstás escribiendo tu primera prueba unitaria en Rust. Todo es lógico: creas una estructura, implementas un método, y luego — el momento solemne de la verificación. Insertas println! en la prueba para asegurarte de que los datos son correctos. Ejecutas cargo test... Y solo ves una línea seca: "test result: ok. 1 passed; 0 failed". Ni un solo "Hello, world!", ningún valor intermedio. Tu println! simplemente ha desaparecido.
¿Te suena familiar? Bienvenido al club. Esta pregunta en Stack Overflow ha acumulado miles de visitas, y es una verdadera piedra de tropiezo para todos los que estudian pruebas en Rust. En este artículo no solo diremos "usa --nocapture". Analizaremos por qué sucede esto, cómo funciona la captura de salida (capture) en el ejecutor de pruebas, y cómo depurar el código correctamente sin violar los principios de las pruebas unitarias.
¿Por qué Rust "silencia" println! en las pruebas?
\n\nNo es un error ni una mala intención de los creadores del lenguaje. Es una decisión arquitectónica consciente. Cuando ejecutas cargo test, Rust hace que las pruebas sean limpias y predecibles. Imagina que tienes 500 pruebas, y cada una imprime 10 líneas de depuración en stdout. Tu terminal se convertirá en un caos y, lo que es más importante, no podrás distinguir los errores reales de la basura de depuración.
Por defecto, el ejecutor de pruebas (la biblioteca test) intercepta toda la salida estándar (std::io::stdout()) y la salida de error estándar (std::io::stderr()). Lo hace a nivel de descriptores de sistema. println! escribe precisamente en stdout, por lo que su salida termina en la "caja negra" del ejecutor. La salida solo se muestra si la prueba falla — para que puedas ver qué salió mal.
¿Cómo se ve esto en el código?
\n\nVeamos un ejemplo simple que reproduce el problema:
\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!("¡Me estoy ejecutando, pero no me ven!");\n let result = add_two(2);\n println!("Resultado: {}", result);\n assert_eq!(result, 4);\n }\n}\n\nEjecutamos:
\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\nComo puedes ver, ninguno de los mensajes de println! apareció. La prueba pasó con éxito y el ejecutor decidió que no había nada que mostrarte.
¿Cómo "despertar" a println!? Métodos y trampas
\n\nExisten tres enfoques principales, y cada uno es bueno para su situación.
\n\nMétodo 1: La bandera --nocapture (la más simple y popular)
\n\nEsto es lo que encontrarás en la primera respuesta de Stack Overflow. La bandera --nocapture desactiva la captura de salida para todas las pruebas en la ejecución actual.
$ cargo test -- --nocapture\n\nObserva el doble guion --. Separa los argumentos para cargo y para el binario de pruebas en sí. Todo lo que está después de -- se pasa al ejecutor de pruebas.
Ahora la salida será así:
\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¡Me estoy ejecutando, pero no me ven!\nResultado: 4\ntest tests::test_add_two ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered