Testes de Integração Rust com Docker: Nível Avançado
Testes de integração são uma etapa crítica no desenvolvimento em Rust, especialmente quando sua aplicação interage com serviços externos: bancos de dados, filas de mensagens, HTTP-API. O uso do Docker permite criar ambientes isolados e reproduzíveis para testes. Neste artigo, abordaremos técnicas avançadas: desde a configuração básica até a execução paralela de testes com testcontainers e docker-compose.
Por que Docker para testes de integração?
Mocks e stubs tradicionais muitas vezes não refletem o comportamento real das dependências. O Docker resolve esse problema:
- Isolamento: cada teste é executado em um contêiner limpo.
- Reprodutibilidade: mesmo ambiente em todas as máquinas dos desenvolvedores e no CI.
- Paralelismo: é possível executar vários bancos de dados de teste simultaneamente.
1. Configuração básica: testando com PostgreSQL
Vamos começar com algo simples: testar uma função que trabalha com PostgreSQL. Usaremos a crate testcontainers — um wrapper Rust para gerenciar contêineres Docker.
Adicione as dependências no Cargo.toml:
[dev-dependencies]testcontainers = "0.18"tokio = { version = "1", features = ["full"] }sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }Exemplo de teste:
use sqlx::PgPool;use testcontainers::clients::Cli;use testcontainers::images::postgres::Postgres;
#[tokio::test]async fn test_database_connection() { let docker = Cli::default(); let postgres_instance = docker.run(Postgres::default()); let connection_string = &format!( "postgres://postgres:postgres@127.0.0.1:{}/postgres", postgres_instance.get_host_port_ipv4(5432) );
let pool = PgPool::connect(connection_string).await.unwrap();
// Executando consulta SQL let row: (i32,) = sqlx::query_as("SELECT 1 + 1") .fetch_one(&pool) .await .unwrap(); assert_eq!(row.0, 2);}Importante: o contêiner é automaticamente removido após a conclusão do teste (graças à implementação Drop).
2. Configuração avançada: imagem personalizada com migrações
Frequentemente é necessário executar migrações previamente. Vamos criar uma imagem personalizada baseada em um Dockerfile:
# Dockerfile.testFROM postgres:16-alpineCOPY migrations/ /docker-entrypoint-initdb.d/No teste Rust, use Image:
use testcontainers::images::generic::GenericImage;
#[tokio::test]async fn test_with_migrations() { let docker = Cli::default(); let image = GenericImage::new("my-app-test:latest") .with_wait_for(testcontainers::core::WaitFor::message_on_stdout( "database system is ready to accept connections" )); let instance = docker.run(image); // ... usando instance}Para construir a imagem no CI, use docker build -f Dockerfile.test -t my-app-test:latest .
3. Execução paralela de testes com Docker Compose
Se seu serviço requer várias dependências (PostgreSQL + Redis), use docker-compose. Crie o arquivo docker-compose.test.yml:
version: '3.8'services: postgres: image: postgres:16-alpine environment: POSTGRES_PASSWORD: testpass ports: - "5432" redis: image: redis:7-alpine ports: - "6379"No teste Rust, levante o ambiente Compose:
use testcontainers::clients::Cli;use testcontainers::images::compose::Compose;
#[tokio::test]async fn test_with_compose() { let docker = Cli::default(); let compose = Compose::new("docker-compose.test.y