Introdução: Por que testar em Rust é simples e confiável
Rust foi projetado desde o início como uma linguagem que ajuda a escrever código seguro e confiável. No entanto, mesmo o sistema de tipos mais rigoroso não garante a correção da lógica de negócios. É aqui que os testes entram em ação. Em Rust, o processo de escrever testes está integrado diretamente na ferramenta cargo, tornando-o incrivelmente conveniente e acessível.
Neste artigo, abordaremos três níveis de teste: unit (unitário), integration (integração) e e2e (end-to-end). Você aprenderá como escrever testes, quando usar cada abordagem e como automatizar a verificação do seu código. O material será útil tanto para iniciantes quanto para desenvolvedores experientes que estão migrando para Rust.
Testes unitários: Testando por dentro
Testes unitários são o tipo mais básico e comum de teste. Eles verificam funções, métodos ou módulos individuais isolados do resto do sistema. Em Rust, é comum escrever testes unitários no mesmo arquivo que o código testado, ou em um arquivo separado tests.rs dentro do módulo.
Como escrever testes unitários em Rust
Para criar um teste, usa-se o atributo #[test]. Os testes são colocados em um módulo com o atributo #[cfg(test)], que garante que o código de teste não seja incluído na compilação final.
// src/lib.rs
pub 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_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide_success() {
let result = divide(10.0, 2.0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 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");
}
}
Executando testes unitários
Você pode executar os testes com um único comando:
cargo test
A saída mostrará quantos testes passaram e quantos falharam. Para executar um teste específico, use um filtro:
cargo test test_add
Testes unitários são ideais para verificar casos limite, tratamento de erros e a correção de algoritmos. Eles são rápidos e não requerem dependências externas.
Testes de integração: Verificando a interação entre módulos
Testes de integração verificam como diferentes módulos da sua biblioteca ou aplicação funcionam juntos. Ao contrário dos testes unitários, eles não têm acesso a funções internas (a menos que estejam exportadas como pub). Testes de integração são escritos em uma pasta separada tests/ no nível raiz do projeto.
Estrutura dos testes de integração
Crie uma pasta tests/ na raiz do seu projeto e adicione arquivos de teste nela. Cada arquivo será compilado como um crate separado.
// tests/integration_test.rs
use my_crate; // nome do seu crate
#[test]
fn test_add_integration() {
assert_eq!(my_crate::add(10, 20), 30);
}
#[test]
fn test_divide_integration() {
let result = my_crate::divide(100.0, 4.0);
assert_eq!(result.unwrap(), 25.0);
}
Módulo comum para testes
Se vários testes de integração usam as mesmas funções auxiliares, coloque-as em um módulo comum. Para isso, crie a pasta tests/common/mod.rs:
// tests/common/mod.rs
pub fn setup() {
// inicialização do logger, banco de dados, etc.
println!("Setting up test environment...");
}
// tests/another_test.rs
mod common;
#[test]
fn test
```