Por que println! silencia nos testes Rust? Análise completa

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: println! "mudo" — a dor familiar de todo iniciante em Rust

\n\n

Você 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.

\n\n

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.

\n\n

Por que o Rust "cala" o println! nos testes?

\n\n

Nã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.

\n\n

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.

\n\n

Como isso se parece no código?

\n\n

Vamos 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\n

Executamos:

\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

Como você pode ver, nenhuma das mensagens println! apareceu. O teste passou com sucesso, e o executor decidiu que não havia nada para mostrar.

\n\n

Como "despertar" o println!? Métodos e armadilhas

\n\n

Existem três abordagens principais, e cada uma é boa para sua situação.

\n\n

Mé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.

\n\n
$ cargo test -- --nocapture
\n\n

Observe 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.

\n\n

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                

Blogs

Book Recommendations