Introdução: Por que Testar em Rust é a Base para Código Confiável
Rust é uma linguagem de programação de sistemas conhecida por sua segurança de memória e desempenho. No entanto, mesmo o compilador mais rigoroso não garante que seu programa faça exatamente o que é necessário. É aqui que os testes entram em cena. Em Rust, o suporte nativo a testes está entre os melhores das linguagens modernas. Você pode escrever testes unitários, testes de integração e testes e2e (ponta a ponta) sem precisar de bibliotecas de terceiros.
Neste artigo, vamos detalhar todos os três níveis de teste com exemplos práticos. Você aprenderá como escrever testes, como executá-los usando cargo test e como organizar a infraestrutura de teste para projetos grandes.
1. Testes Unitários em Rust: Testando Funções e Módulos
Testes unitários verificam a operação de funções ou métodos individuais de forma isolada. Em Rust, é comum colocar testes unitários diretamente no arquivo de código fonte, dentro de um módulo tests com o atributo #[cfg(test)]. Isso permite que os testes acessem funções privadas do módulo.
1.1. Seu Primeiro Teste Unitário
Vamos criar uma biblioteca de cálculo simples e escrever testes para ela.
// src/lib.rspub fn add(a: i32, b: i32) -> i32 { a + b}
pub fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("Division by zero")) } else { Ok(a / b) }}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_add() { assert_eq!(add(2, 3), 5); }
#[test] fn test_divide_success() { let result = divide(10.0, 2.0).unwrap(); assert_eq!(result, 5.0); }
#[test] fn test_divide_by_zero() { let result = divide(10.0, 0.0); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Division by zero"); }}Execute os testes com o comando cargo test. Você verá marcas verdes se todos os testes passarem.
1.2. Atributos de Teste
#[test]— indica que a função é um teste.#[should_panic]— o teste é considerado aprovado se a função entrar em pânico.#[ignore]— desabilita temporariamente o teste.
#[test]#[should_panic(expected = "panic message")]fn test_panic() { panic!("panic message");}
#[test]#[ignore = "Test disabled until bug #42 is fixed"]fn test_expensive() { // Long-running test}2. Testes de Integração: Verificando a Interação entre Componentes
Testes de integração verificam a operação de múltiplos módulos ou dependências externas juntos. Em Rust, eles são colocados em um diretório separado tests/ na raiz do projeto. Cada arquivo nesta pasta é uma crate de teste separada que só enxerga a API pública da sua biblioteca.
2.1. Estrutura dos Testes de Integração
Crie uma pasta tests/ e um arquivo tests/integration_test.rs:
// tests/integration_test.rsuse my_crate::add; // Nome da sua crate
#[test]fn test_add_integration() { // Verifica se a função funciona como parte da biblioteca let result = add(10, 20); assert_eq!(result, 30);}
#[test]fn test_divide_integration() { // Teste com múltiplas chamadas let a = 100.0; let b = 4.0; let result = my_crate::divide(a, b).unwrap(); assert!(result > 0.0); assert_eq!(result, 25.0);}2.2. Módulos Compartilhados para Testes de Integração
Se você precisar reutilizar código entre testes de integração, crie um arquivo tests/common/mod.rs. Isso não será tratado como um arquivo de teste, mas você poderá importá-lo de outros testes.
// tests/common/mod.rspub fn setu