Testes em Rust: de testes unitários a property-based testing
Rust é uma das poucas linguagens onde o sistema de tipos e propriedade já na etapa de compilação exclui classes inteiras de erros (condições de corrida, ponteiros pendentes, dupla liberação de memória). No entanto, erros lógicos, tratamento incorreto de casos extremos e lógica de negócio incorreta permanecem. Para identificá-los, o ecossistema Rust desenvolveu um conjunto poderoso de ferramentas de teste — desde o executor de testes integrado até frameworks avançados para geração de dados de teste.
Ferramentas integradas: cargo test
A ferramenta básica de teste está integrada diretamente no compilador e no gerenciador de pacotes. Para criar testes, basta adicionar um módulo com o atributo #[cfg(test)] e marcar funções com o atributo #[test].
// src/lib.rspub fn add(left: u64, right: u64) -> u64 { left + right}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_add() { let result = add(2, 2); assert_eq!(result, 4); }
#[test] fn test_add_negative() { // u64 não suporta números negativos — isso é um erro de compilação // Mas se a função aceitasse i64: // assert_eq!(add(-2, 2), 0); }}Execução: cargo test. A flag -- --nocapture exibe println! dos testes. A flag -- --test-threads=1 desativa a execução paralela.
Testes de integração
Testes de integração estão localizados na pasta tests/ no nível raiz do projeto. Cada arquivo é um crate separado que pode testar a API pública da biblioteca.
// tests/integration_test.rsuse my_library::add;
#[test]fn test_integration() { assert_eq!(add(10, 20), 30);}Para testar crates binários (com main.rs), pode-se usar assert_cmd — ele executa o binário como um processo externo e verifica stdout/stderr.
Property-based testing: proptest e quickcheck
Em vez de escrever dezenas de casos de teste manualmente, o property-based testing gera milhares de dados de entrada aleatórios e verifica invariantes. Dois frameworks principais:
- proptest — moderno, flexível, com suporte a estratégias, filtragem e redução de exemplos (shrink).
- quickcheck — mais antigo, inspirado no Haskell QuickCheck, mais simples, mas menos poderoso.
Exemplo com proptest:
use proptest::prelude::*;
fn reverse_string(s: &str) -> String { s.chars().rev().collect()}
proptest! { #[test] fn test_reverse_twice(s in ".*") { let reversed = reverse_string(&s); let double_reversed = reverse_string(&reversed); assert_eq!(s, double_reversed); }
#[test] fn test_reverse_length(s in ".*") { let reversed = reverse_string(&s); assert_eq!(s.len(), reversed.len()); }}Se o teste falhar, proptest encontra o contraexemplo mínimo (shrinking).
Teste de código assíncrono: tokio-test
Para funções assíncronas que trabalham com tokio, testes comuns não são adequados — é necessário um runtime assíncrono. tokio-test fornece a macro #[tokio::test].
use tokio::time::{sleep, Duration};
async fn delayed_add(a: u32, b: u32) -> u32 { sleep(Duration::from_millis(10)).await; a + b}
#[tokio::test]async fn test_delayed_add() { let result = delayed_add(3, 5).await; assert_eq!(result, 8);}Também é possível usar futures-test para trabalhar com futures e async-std.
Mocks e stubs: mockall e mockito
Para isolar testes de dependências externas (BD, API, sistema de arquivos
Também na biblioteca
Parsing de HTML em PHP: DOMDocument, SimpleXML e cURL para Web Scraping
Cliente HTTP para Go (Golang): principais bibliotecas para trabalhar com HTTP
Trabalhando com Excel em Go (Golang): bibliotecas exelize e tealeg/xlsx
Trabalhando com CSV em C++: bibliotecas, parsing e escrita de dados