Testes de Integração Rust com Docker: da Teoria à Prática
O desenvolvimento moderno em Rust requer não apenas a verificação de funções individuais (testes unitários), mas também o teste da interação dos componentes com serviços externos — bancos de dados, caches, APIs. Os testes de integração com Docker permitem isolar o ambiente, garantir reprodutibilidade e evitar problemas de "funciona na minha máquina". Neste artigo, vamos analisar como configurar testes de integração para aplicações Rust usando Docker.
Por que Docker para testes de integração Rust?
O Docker resolve três problemas principais:
- Isolamento do ambiente — os testes são executados em um contêiner limpo, sem influência das configurações locais
- Reprodutibilidade — condições idênticas em todas as máquinas dos desenvolvedores e no CI/CD
- Gerenciamento de dependências — inicialização automática de PostgreSQL, Redis, Kafka e outros serviços
Configuração do projeto: estrutura e dependências
Vamos criar um projeto com testes de integração para um servidor web em Actix-web que funciona com PostgreSQL.
# Cargo.toml[package]name = "rust-docker-testing"version = "0.1.0"edition = "2021"
[dependencies]actix-web = "4"serde = { version = "1", features = ["derive"] }tokio = { version = "1", features = ["full"] }sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }dotenv = "0.15"
[dev-dependencies]testcontainers = "0.14"reqwest = { version = "0.11", features = ["json"] }Observe o testcontainers — esta é uma biblioteca Rust para gerenciar contêineres Docker diretamente a partir dos testes.
Estrutura do projeto
Estrutura recomendada para testes de integração:
.├── src/│ ├── main.rs│ ├── lib.rs│ ├── models.rs│ └── handlers.rs├── tests/│ ├── common/│ │ └── mod.rs # Utilitários comuns para testes│ ├── integration_tests.rs # Testes de integração principais│ └── docker-compose.yml # Para execução manual├── Dockerfile└── .envPasso 1: Escrevendo um teste de integração com Docker
Vamos criar um teste que sobe PostgreSQL no Docker, executa migrações e verifica a API.
// tests/integration_tests.rsuse testcontainers::clients;use testcontainers::images::postgres::Postgres;use sqlx::postgres::PgPoolOptions;use std::net::TcpListener;
async fn setup_test_db() -> (String, testcontainers::Container) { let docker = clients::Cli::default(); let postgres_image = Postgres::default(); let container = docker.run(postgres_image); let host = container.get_host().unwrap(); let port = container.get_host_port_ipv4(5432).unwrap(); let database_url = format!("postgres://postgres:postgres@{}:{}/postgres", host, port); // Executa as migrações let pool = PgPoolOptions::new() .max_connections(5) .connect(&database_url) .await .expect("Falha ao conectar ao Postgres"); sqlx::query("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL)") .execute(&pool) .await .expect("Migração falhou"); (database_url, container)}
#[tokio::test]async fn test_create_user() { let (db_url, _container) = setup_test_db().await; // Inicia a aplicação em uma porta aleatória let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let server = actix_web::HttpServer::new(move || { actix_web::App::new() .app_data(actix_web::web::Data::new(db_url.clone())) .route("/users", actix_web::web::post().to(handlers::create_user)) }) .listen(listener) .unwrap() .run(); tokio::spawn(server); // Aguarda a inicialização do servidor tokio::ti
```