Rust Admin

O que Todo Dev Deve Saber sobre Testes Unitários e de Integração em Rust com cargo test Já leu

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 e da função 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 . 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 para organizar um módulo de testes que só compila quando você executa . Aqui está um exemplo real: A macro compara valores, valida booleanos, e verifica se o código falha corretamente. Use e para

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ários
  • cargo test --test * — apenas testes de integração
  • cargo test adicionar — filtra por nome (roda teste_adicionar_positivos, etc.)
  • cargo test -- --test-threads=1 — executa serialmente (útil para debugging)
  • cargo test -- --nocapture — mostra println! mesmo em testes passando
  • cargo 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.

Referências


Artigos relacionados