Testes em Rust: Unit, Integration, E2E em nível avançado

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Testes avançados em Rust: de unit a E2E

Testes são uma parte essencial do desenvolvimento de software de sistema confiável em Rust. Se os conceitos básicos (escrever testes simples com #[test]) são familiares a todos, as técnicas avançadas — trabalho com mocks, parametrização, atributos personalizados e organização de testes E2E — exigem uma análise separada. Neste artigo, veremos como elevar os testes em Rust a um nível profissional, usando abordagens unit, integration e end-to-end.



1. Testes unitários: mock, parametrização e atributos personalizados

Testes unitários em Rust geralmente são escritos no mesmo arquivo que o código, no módulo tests. No entanto, em um nível avançado, surge a necessidade de isolar o código testado de dependências externas.



Objetos mock com traits

Para criar mocks em Rust, geralmente se usam traits. Você define um trait, implementa-o para o objeto real e para o mock de teste. Crates populares: mockall e mockito (para HTTP).

use mockall::automock;

#[automock]trait Database { fn get_user(&self, id: u32) -> String;}

fn process_user(db: &dyn Database, id: u32) -> String { let user = db.get_user(id); format!("User: {}", user)}

#[cfg(test)]mod tests { use super::*;

#[test] fn test_process_user() { let mut mock = MockDatabase::new(); mock.expect_get_user() .with(predicate::eq(42)) .returning(|_| "Alice".to_string()); assert_eq!(process_user(&mock, 42), "User: Alice"); }}


Testes parametrizados

Repetir o mesmo teste com diferentes dados de entrada é uma tarefa comum. O crate rstest fornece uma solução elegante.

use rstest::rstest;

#[rstest]#[case(2, 3, 5)]#[case(0, 0, 0)]#[case(-1, 1, 0)]fn test_add(#[case] a: i32, #[case] b: i32, #[case] expected: i32) { assert_eq!(a + b, expected);}


Atributos de teste personalizados

Às vezes, é necessária lógica antes ou depois do teste. Você pode criar seus próprios atributos através de macros procedurais, mas é mais simples usar o crate test-case ou ctor para inicialização.

// Exemplo com ctor para inicialização do logger#[cfg(test)]mod tests {    use ctor::ctor;

#[ctor] fn init() { // Inicialização única antes de todos os testes }}


2. Testes de integração: organização e trabalho com dependências externas

Testes de integração em Rust estão localizados na pasta tests/ no nível raiz do projeto. Cada arquivo lá é um crate separado. Técnicas avançadas incluem módulos compartilhados e testes com bancos de dados reais ou APIs.



Utilitários compartilhados para testes de integração

Para evitar duplicação de código (por exemplo, configuração de servidor de teste), crie um módulo common dentro de tests/.

// tests/common/mod.rspub fn setup_test_db() -> String {    // Configuração do banco de dados de teste    "connected".to_string()}

// tests/integration_test.rsmod common;

#[test]fn test_db_connection() { let db = common::setup_test_db(); assert_eq!(db, "connected");}


Trabalhando com requisições HTTP

Para testar clientes HTTP, use mockito — ele levanta um servidor mock local.

use mockito::{mock, server_url};

#[tokio::test]async fn test_api_call() { let _m = mock("GET", "/users/1") .with_status(200) .with_body(r#"{"id":1,"name":"Alice"}"#) .create();

let client = reqwest::Client::new(); let resp = client.get(&format!("{}/users/1", server_url())) .send() .await .unwrap(); assert!(resp.status().is_success());}


3. Testes E2E: ciclo completo da aplicação

Testes end-to-end verificam o funcionamento do sistema como um todo: desde a inicialização

Blogs

Book Recommendations