Fundamentos de Testes em Rust
Testes em Rust são nativos à linguagem e totalmente integrados ao ecossistema via Cargo, o gerenciador de pacotes. Diferentemente de outras linguagens, você não precisa instalar frameworks externos para começar. A stdlib de Rust fornece tudo necessário através do atributo #[test] e da função assert!() e variações.
O Cargo oferece dois tipos principais: testes unitários (escritos dentro dos módulos que testam) e testes de integração (em arquivos separados, testando a API pública). Executar testes é tão simples quanto cargo test. Rust compila testes como binários especiais e os executa em paralelo por padrão, tornando o feedback extremamente rápido mesmo em projetos maiores.
Testes Unitários: Estrutura Básica
Escrevendo seu primeiro teste unitário
Testes unitários vivem no mesmo arquivo do código que testam. Use o atributo #[cfg(test)] para organizar um módulo de testes que só compila quando você executa cargo test. Aqui está um exemplo real:
// src/lib.rs
pub fn adicionar(a: i32, b: i32) -> i32 {
a + b
}
pub fn dividir(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Divisão por zero".to_string())
} else {
Ok(a / b)
}
}
#[cfg(test)]
mod testes {
use super::*;
#[test]
fn teste_adicionar_positivos() {
assert_eq!(adicionar(2, 3), 5);
}
#[test]
fn teste_adicionar_negativos() {
assert_eq!(adicionar(-1, -1), -2);
}
#[test]
fn teste_divisao_sucesso() {
assert_eq!(dividir(10, 2).unwrap(), 5);
}
#[test]
fn teste_divisao_por_zero() {
assert!(dividir(10, 0).is_err());
}
#[test]
#[should_panic(expected = "Divisão por zero")]
fn teste_divisao_panic() {
if let Err(e) = dividir(10, 0) {
panic!("{}", e);
}
}
}
A macro assert_eq!() compara valores, assert!() valida booleanos, e #[should_panic] verifica se o código falha corretamente. Use unwrap() e is_err() para trabalhar com Result em testes. Execute cargo test e você verá cada teste executado individualmente com feedback colorido.
Truques úteis
Use #[ignore] para pular testes durante desenvolvimento: #[test] #[ignore]. Execute apenas testes ignorados com cargo test -- --ignored. Para rodar um teste específico: cargo test nome_do_teste. Use cargo test -- --nocapture para ver saídas println! durante testes.
Testes de Integração
Estrutura e organização
Testes de integração testam sua biblioteca como um usuário externo faria. Eles ficam em um diretório especial tests/ na raiz do projeto. Cada arquivo .rs dentro de tests/ é compilado como um crate binário separado. Isso garante que você teste apenas a API pública.
Crie a estrutura: seu Cargo.toml fica em /, src/lib.rs em /src, e testes de integração em /tests/*.rs. Diferentemente de testes unitários, você não usa #[cfg(test)] nem módulos privados. Importe sua lib normalmente:
// tests/integracao.rs
use seu_projeto::adicionar;
use seu_projeto::dividir;
#[test]
fn teste_fluxo_completo_adicionar() {
let resultado = adicionar(5, 5);
assert_eq!(resultado, 10);
}
#[test]
fn teste_fluxo_divisao_com_validacao() {
match dividir(20, 4) {
Ok(res) => assert_eq!(res, 5),
Err(_) => panic!("Divisão falhou inesperadamente"),
}
}
#[test]
fn teste_integracao_multiplas_operacoes() {
let soma = adicionar(10, 20);
let resultado = dividir(soma, 5).expect("Divisão falhou");
assert_eq!(resultado, 6);
}
Execute cargo test --test integracao para rodar apenas esse arquivo de testes. Se você tiver múltiplos arquivos em tests/, cada um é executado independentemente. Isso é poderoso para testar cenários complexos que envolvem múltiplas funções públicas trabalhando juntas.
Cargo test: Controle Total
Executando e filtrando testes
O comando cargo test é extremamente flexível. Execute cargo test -- --help para ver todas as opções. Alguns comandos práticos:
cargo test— executa todos os testes (unitários e integração)cargo test --lib— apenas testes unitárioscargo test --test *— apenas testes de integraçãocargo test adicionar— filtra por nome (rodateste_adicionar_positivos, etc.)cargo test -- --test-threads=1— executa serialmente (útil para debugging)cargo test -- --nocapture— mostraprintln!mesmo em testes passandocargo test -- --show-output— alias moderno do acima
Use cargo test --release para compilar otimizado (mais lento na compilação, mais rápido na execução). Perfeito para suites grandes ou testes de performance.
Boas práticas com Cargo
Organize seus testes de integração em subdiretórios lógicos dentro de tests/. Por exemplo: tests/operacoes/ e tests/validacoes/ com um mod.rs em cada. Isso mantém a estrutura clara conforme sua suite cresce. Lembre-se: código em tests/ é compilado como crates independentes, então cada arquivo precisa ser autocompleto.
Conclusão
Aprendemos três pilares fundamentais: (1) testes unitários usam #[cfg(test)] no mesmo arquivo, oferecendo isolamento e velocidade; (2) testes de integração ficam em tests/ e testam a API pública como um usuário externo faria; (3) cargo test oferece controle fino sobre execução — filtros, paralelização, output — tudo integrado. Combine essas três práticas com disciplina e você terá confiança máxima no seu código Rust.