Testes de integração e doc tests
Nesta aula, você aprenderá a criar testes de integração em Rust usando a pasta tests/, além de escrever documentação com testes embutidos (doc tests). Também verá como organizar e medir a cobertura de testes no projeto.
Nesta aula, vamos explorar duas ferramentas essenciais para garantir a qualidade do seu código Rust: os testes de integração e os doc tests. Enquanto os testes unitários verificam partes isoladas do código, os testes de integração validam a interação entre módulos e o comportamento externo da sua biblioteca ou binário. Já os doc tests permitem que exemplos contidos na documentação sejam executados como testes, garantindo que a documentação nunca fique desatualizada.
Vamos começar entendendo a estrutura de pastas que o Rust espera para testes de integração e, em seguida, mergulharemos nos doc tests, que são uma prática poderosa para manter a documentação viva. Ao final, discutiremos como organizar seus testes e como obter uma visão geral da cobertura de código.
Pasta tests/
Em um projeto Rust, a pasta tests/ é um local especial onde você coloca testes de integração. Diferente dos testes unitários (que ficam dentro dos arquivos de código-fonte, geralmente em um módulo #[cfg(test)]), os testes de integração são arquivos .rs separados que estão fora da pasta src/. Cada arquivo dentro de tests/ é compilado como um crate separado e pode acessar a sua biblioteca como se fosse um usuário externo.
Para criar um teste de integração, basta criar um arquivo dentro de tests/. Por exemplo, se você tem uma biblioteca chamada my_lib, pode criar tests/integration.rs e escrever:
// tests/integration.rs
use my_lib::add;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
Para que isso funcione, sua biblioteca precisa expor os itens publicamente (com pub). Os testes de integração são executados com cargo test e são ideais para testar o comportamento da API pública, cenários de uso real e interações entre módulos.
Uma vantagem dos testes de integração é que eles podem testar a sua crate como um consumidor externo, garantindo que a interface pública seja utilizável e que os módulos internos não vazem detalhes desnecessários. Isso ajuda a manter um design limpo e a detectar problemas que os testes unitários podem não pegar.
Doc tests
Os doc tests são testes escritos dentro da documentação do seu código, usando a sintaxe de blocos de código Markdown com a linguagem especificada como rust. Quando você executa cargo test, o Rust também compila e executa esses exemplos como testes. Isso garante que os exemplos na documentação estão corretos e funcionam como esperado.
Para criar um doc test, basta adicionar um bloco de código em um comentário de documentação (///) ou em um comentário de módulo (//!). Por exemplo:
/// Adiciona dois números.
///
/// # Exemplos
///
/// ```
/// use my_lib::add;
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
No exemplo acima, o bloco de código dentro da documentação é um doc test. O Rust irá compilá-lo e executá-lo, verificando que as afirmações são verdadeiras. Você também pode usar a anotação ```rust,ignore para ignorar um bloco, ou ```rust,no_run para compilar mas não executar.
Os doc tests são uma forma excelente de manter a documentação viva e precisa. Eles incentivam a escrever exemplos que realmente funcionam, e qualquer alteração no código que quebre um exemplo será detectada nos testes. Isso é especialmente útil para bibliotecas públicas, onde a documentação é a principal forma de os usuários aprenderem a usar o código.
Organização
Conforme seu projeto cresce, é importante organizar os testes de forma consistente. Para testes de integração, você pode criar subpastas dentro de tests/ para agrupar testes por funcionalidade. Por exemplo:
tests/
├── common/
│ └── mod.rs
├── integration.rs
└── api/
└── test_auth.rs
O arquivo common/mod.rs pode conter funções auxiliares compartilhadas entre vários testes de integração. Para usá-lo, você pode declarar mod common; em cada arquivo de teste. Lembre-se de que cada arquivo em tests/ é um crate separado, então você precisa incluir os módulos explicitamente.
Para testes unitários, a organização tradicional é colocar um módulo tests dentro de cada arquivo de código-fonte, ou em um arquivo src/tests.rs que é incluído com #[cfg(test)] mod tests;. Isso mantém os testes próximos do código que estão testando, facilitando a manutenção.
Outra prática comum é usar a ferramenta cargo test com filtros para executar apenas um subconjunto de testes, por exemplo cargo test test_add para rodar apenas o teste com nome contendo test_add. Isso ajuda a depurar rapidamente um teste específico.
Cobertura (visão geral)
A cobertura de código é uma métrica que indica qual porcentagem do seu código é exercitada pelos testes. Embora o Rust não tenha uma ferramenta de cobertura embutida, existem ferramentas externas como tarpaulin (para Linux) ou cargo-llvm-cov (que usa a instrumentação LLVM). Essas ferramentas geram relatórios detalhados mostrando quais linhas, funções e branches foram executados durante os testes.
Para instalar o cargo-llvm-cov, você pode usar cargo install cargo-llvm-cov e depois executar cargo llvm-cov no seu projeto. Ele coleta a cobertura de todos os testes, incluindo unitários, integração e doc tests. Exemplo de uso:
cargo llvm-cov --open
Esse comando gera um relatório em HTML e o abre no navegador. Você pode ver quais partes do código não foram testadas e adicionar testes para melhorar a cobertura.
Uma cobertura alta não garante a ausência de bugs, mas é um indicador útil de que seu código está sendo exercitado. É importante lembrar que a cobertura deve ser usada como um guia, não como um objetivo cego. Priorize testar os caminhos críticos e os casos de borda.
Boas práticas
Sempre que possível, escreva doc tests para funções públicas, pois eles servem como documentação viva e testes ao mesmo tempo. Para testes de integração, foque em cenários de uso real e na interação entre módulos. Mantenha os testes organizados e use nomes descritivos. Além disso, execute os testes com frequência e monitore a cobertura para identificar lacunas.
Referências
- The Rust Programming Language - Test Organization
- Rustdoc Documentation Tests
- Cargo Guide - Tests
- Tarpaulin - Code Coverage Tool
- cargo-llvm-cov - LLVM-based code coverage
Exercícios
Crie uma biblioteca simples chamada
calculatorcom uma função públicaadd(a: i32, b: i32) -> i32e um teste de integração emtests/que verifique a soma de dois números.✓ Resposta: Crie o projeto comcargo new calculator --lib. Emsrc/lib.rs, definapub fn add(a: i32, b: i32) -> i32 { a + b }. Crietests/integration.rscom:use calculator::add; #[test] fn test_add() { assert_eq!(add(2, 3), 5); }Escreva um doc test para a função
adddo exercício anterior, garantindo que o exemplo na documentação é executado e passa.✓ Resposta: Adicione um comentário de documentação emsrc/lib.rsantes da função:/// Soma dois números. /// /// # Exemplos /// /// ``` /// use calculator::add; /// let result = add(2, 3); /// assert_eq!(result, 5); /// ``` pub fn add(a: i32, b: i32) -> i32 { a + b }Organize seus testes de integração em subpastas: crie
tests/api/mod.rscom um teste etests/common/mod.rscom uma função utilitária. Use essa função no teste.✓ Resposta: Estrutura:
Emtests/ ├── api/ │ └── mod.rs └── common/ └── mod.rstests/common/mod.rs:
Empub fn setup() -> i32 { 42 }tests/api/mod.rs:use calculator::add; mod common; #[test] fn test_add_with_common() { let x = common::setup(); assert_eq!(add(x, 1), 43); }Execute
cargo teste observe a saída. Identifique quantos testes foram executados e se todos passaram. Em seguida, adicione um teste que falhe de propósito e veja o relatório.✓ Resposta: A saída do cargo test mostrará algo como:
Para um teste que falha, basta usarrunning 1 test test test_add ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered outassert_eq!(add(2, 3), 6);e o cargo test indicará a falha com a mensagem de pânico.Instale o cargo-llvm-cov e gere um relatório de cobertura para o seu projeto. Identifique pelo menos uma função ou linha que não está coberta e escreva um teste para cobri-la.
✓ Resposta: Instale comcargo install cargo-llvm-cove executecargo llvm-cov --open. O relatório mostrará a porcentagem de cobertura. Por exemplo, se a funçãoaddestiver coberta, mas outra funçãosubtractnão, adicione um teste para ela.