Introdução: println! "mudo" — a dor familiar de todo iniciante em Rust
\n\nVocê está escrevendo seu primeiro teste unitário em Rust. Tudo lógico: você cria uma estrutura, implementa um método e então — o momento solene da verificação. Você insere println! no teste para garantir que os dados estão corretos. Executa cargo test... E vê apenas uma linha seca: "test result: ok. 1 passed; 0 failed". Nenhum "Hello, world!", nenhum valor intermediário. Seu println! simplesmente desapareceu.
Familiar? Bem-vindo ao clube. Esta pergunta no Stack Overflow acumulou milhares de visualizações, e é uma verdadeira pedra no sapato para todos que estudam testes em Rust. Neste artigo, não vamos apenas dizer "use --nocapture". Vamos analisar por que isso acontece, como funciona a captura de saída (capture) no executor de testes e como depurar o código corretamente sem violar os princípios de teste unitário.
Por que o Rust "cala" o println! nos testes?
\n\nNão é um bug nem uma má intenção dos criadores da linguagem. É uma decisão arquitetural consciente. Quando você executa cargo test, o Rust torna os testes limpos e previsíveis. Imagine que você tem 500 testes, e cada um imprime 10 linhas de depuração no stdout. Seu terminal viraria uma bagunça e, principalmente, você não conseguiria distinguir erros reais do lixo de depuração.
Por padrão, o executor de testes (biblioteca test) intercepta toda a saída padrão (std::io::stdout()) e a saída de erro padrão (std::io::stderr()). Ele faz isso no nível dos descritores de sistema. println! escreve exatamente no stdout, então sua saída vai para a "caixa preta" do executor. A saída só é exibida se o teste falhar — para que você possa ver o que deu errado.
Como isso se parece no código?
\n\nVamos ver um exemplo simples que reproduz o 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!("Estou sendo executado, mas não sou visto!");\n let result = add_two(2);\n println!("Resultado: {}", result);\n assert_eq!(result, 4);\n }\n}\n\nExecutamos:
\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 você pode ver, nenhuma das mensagens println! apareceu. O teste passou com sucesso, e o executor decidiu que não havia nada para mostrar.
Como "despertar" o println!? Métodos e armadilhas
\n\nExistem três abordagens principais, e cada uma é boa para sua situação.
\n\nMétodo 1: A flag --nocapture (o mais simples e popular)
\n\nÉ o que você encontrará na primeira resposta no Stack Overflow. A flag --nocapture desativa a captura de saída para todos os testes na execução atual.
$ cargo test -- --nocapture\n\nObserve o travessão duplo --. Ele separa os argumentos para cargo e para o próprio binário de teste. Tudo após -- é passado para o executor de testes.
Agora a saída será assim:
\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\nEstou sendo executado, mas não sou visto!\nResultado: 4\ntest tests::test_add_two ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered