Testes de Integração Rust com Docker: do A ao Z
\n\nTestes de integração são uma etapa criticamente importante no desenvolvimento de aplicações confiáveis em Rust. Ao contrário dos testes unitários, os testes de integração verificam a interação dos componentes com dependências externas: bancos de dados, caches, filas de mensagens. O Docker é ideal para criar ambientes de teste isolados e reproduzíveis. Neste artigo, vamos abordar como configurar testes de integração de um projeto Rust usando Docker, começando pela arquitetura básica e indo até CI/CD.
\n\n1. Por que o Docker é a melhor escolha para testes de integração Rust
\n\nRust compila para código nativo, mas suas aplicações geralmente dependem de serviços externos. O Docker resolve três problemas principais:
\n- \n
- Isolamento: cada teste é executado em um contêiner limpo, eliminando a influência de execuções anteriores. \n
- Reprodutibilidade: versões exatas do PostgreSQL, Redis ou Kafka garantem o mesmo comportamento na máquina local e no CI. \n
- Velocidade: as imagens Docker são armazenadas em cache e os testes Rust são compilados uma vez. \n
Vamos considerar um projeto típico: um servidor web em Actix-web, trabalhando com PostgreSQL. Nosso objetivo é escrever um teste que inicie um contêiner com o banco de dados, execute migrações e verifique operações CRUD.
\n\n2. Configuração da infraestrutura de teste com Docker Compose
\n\nPara maior comodidade, usamos docker-compose.yml, que descreve todas as dependências. Vamos criar o arquivo na raiz do projeto:
version: '3.8'\nservices:\n postgres_test:\n image: postgres:15-alpine\n environment:\n POSTGRES_USER: test_user\n POSTGRES_PASSWORD: test_pass\n POSTGRES_DB: test_db\n ports:\n - "5433:5432"\n tmpfs: /var/lib/postgresql/data # dados em memória para velocidade\n\nObserve o tmpfs — isso acelera os testes, pois os dados não são gravados em disco. A porta 5433 foi escolhida para não conflitar com o banco de dados local.
Agora, vamos escrever uma função Rust para gerenciar os contêineres. Usamos a crate testcontainers — ela permite iniciar contêineres Docker programaticamente:
// tests/common/mod.rs\nuse testcontainers::{clients, images::postgres::Postgres, Container};\n\npub struct TestDb {\n pub container: Container<'static, Postgres>,\n pub connection_string: String,\n}\n\nimpl TestDb {\n pub fn new() -> Self {\n let docker = clients::Cli::default();\n let postgres_image = Postgres::default();\n let container = docker.run(postgres_image);\n \n let host = container.get_host();\n let port = container.get_host_port_ipv4(5432);\n let connection_string = format!(\n "postgres://postgres:postgres@{}:{}/postgres",\n host, port\n );\n \n TestDb { container, connection_string }\n }\n}\n\nEste módulo pode ser reutilizado em todos os testes de integração. O contêiner será removido automaticamente se os testes falharem ou quando o processo terminar.
\n\n3. Escrevendo testes de integração com requisições reais
\n\nVamos criar um teste que verifica a inserção e leitura de um usuário. Suponha que temos uma estrutura User e funções create_user e get_user no código principal.
// tests/integration_test.rs\nmod common;\n\nuse sqlx::PgPool;\nuse your_crate::{create_user, get_user, User};\n\n#[tokio::test]\nasync fn test_create_and_get_user() {\n let test_db = common::TestDb::new();\n let pool = PgPool::connect(&test_db.connection_string)\n .await\n .expect("Failed to connect to DB");\n \n // Executa as migrações (se estiver usando sqlx)\n sqlx::migrate!().run(&pool).await.unwrap();\n \n // Cria um usuário\n let user = User {\n name: "Alice".into(),\n email: "alice@example.com".into(),\n };\n let created = create_user(&pool, &us