Introdução: Por que testar em Rust não é apenas moda, mas uma necessidade
\n\nRust é uma linguagem conhecida por sua segurança de memória e alto desempenho. No entanto, mesmo o compilador mais rigoroso não garante a correção da lógica de negócios. É aqui que os testes entram em cena. Em Rust, o sistema de tipos e propriedade já elimina toda uma classe de erros em tempo de compilação, mas os testes unitários, de integração e ponta a ponta (e2e) permitem garantir que seu código faça exatamente o que você planejou.
\n\nNeste artigo, abordaremos três níveis de teste em Rust: desde testes unitários isolados até cenários e2e completos. Você aprenderá como escrever testes eficazes, quais ferramentas usar e como organizar a infraestrutura de teste. O material será útil tanto para iniciantes que estão começando a aprender Rust quanto para desenvolvedores experientes que desejam sistematizar seus conhecimentos.
\n\n1. Testes unitários: a base de tudo
\n\nTestes unitários são o nível de verificação mais rápido e isolado. Eles testam funções individuais, métodos ou pequenos módulos, separados de dependências externas. Em Rust, os testes unitários são geralmente colocados no mesmo arquivo que o código testado, dentro do módulo tests.
1.1. Estrutura e atributos
\n\nPara criar um teste unitário, usa-se o atributo #[test]. A função marcada com ele não aceita argumentos e não retorna nada (ou retorna um Result). Se ocorrer um pânico (panic) dentro da função, o teste é considerado falho.
// src/lib.rs\npub fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_add() {\n assert_eq!(add(2, 2), 4);\n }\n\n #[test]\n fn test_add_negative() {\n assert_eq!(add(-1, -1), -2);\n }\n}\n\nObserve o #[cfg(test)]. Este atributo garante que o módulo com testes seja compilado apenas ao executar os testes (cargo test). Ele não será incluído na compilação de produção.
1.2. Macros para verificações
\n\nRust fornece várias macros para verificar asserções:
\n\n- \n
assert!(expr)— verifica se a expressão é verdadeira. \n assert_eq!(left, right)— verifica igualdade (requerPartialEqeDebug). \n assert_ne!(left, right)— verifica desigualdade. \n panic!("message")— força um pânico (usado para testar erros). \n
1.3. Testando pânicos com should_panic
\n\nSe sua função deve entrar em pânico com dados de entrada inválidos, use o atributo #[should_panic]:
pub fn divide(a: f64, b: f64) -> f64 {\n if b == 0.0 {\n panic!("Division by zero");\n }\n a / b\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n #[should_panic(expected = "Division by zero")]\n fn test_divide_by_zero() {\n divide(10.0, 0.0);\n }\n}\n\nO parâmetro expected é opcional, mas torna o teste mais preciso: o pânico deve conter a string especificada.
2. Testes de integração: verificando a interação entre módulos
\n\nTestes de integração verificam como diferentes partes da sua aplicação funcionam juntas. Ao contrário dos testes unitários, eles ficam em um diretório separado tests/ na raiz do projeto (ao lado de src/). Cada arquivo nesta pasta é compilado como uma crate de biblioteca separada.
2.1. Criando um teste de integração
\n\nCrie um arquivo tests/integration_test.rs:
// tests/integration_test.rs\nuse my_crate::add; // suponha que sua crate se chame my_crate\n\n#[test]\nfn test_add_integration() {\n let result = add(5, 10);\n assert_eq!(result, 15);\n}\n\nPara executar apenas os testes de integração