Acesse o painel da sua conta

Não tem uma conta? Registrar

Entrar em contato

Visite também nosso site craftxp.com.br

  • img
  • img
  • img
  • img
  • img
  • img

Entre em contato

Testcontainers: Testes de Integração Confiáveis para Microsserviços com Docker

Testcontainers: Testes de Integração Confiáveis para Microsserviços com Docker

O Desafio dos Testes de Integração em Microsserviços

Todo desenvolvedor que já trabalhou com microsserviços conhece o dilema: testar a integração real com bancos de dados, filas e APIs externas sem tornar o ambiente de testes frágil, lento ou dependente de infraestrutura remota. Soluções tradicionais como mocks e in-memory databases frequentemente escondem bugs que só aparecem em produção — diferenças sutis de SQL, comportamentos de transações ou latências de rede que um mock simplesmente não reproduz.

É nesse cenário que o Testcontainers se destaca: uma biblioteca que gerencia containers Docker programaticamente dentro dos seus testes, provisionando instâncias reais de serviços (PostgreSQL, Redis, Kafka, RabbitMQ, S3, etc.) que são destruídas automaticamente ao final. Cada suíte de testes ganha um ambiente isolado, idêntico ao de produção e 100% reproduzível.

O que é Testcontainers?

Testcontainers é uma biblioteca open-source disponível para Java, Python (.testcontainers), Go (.testcontainers-go), Node.js (.testcontainers/node) e outras linguagens. Ela utiliza a API do Docker para criar, configurar e destruir containers como parte do ciclo de vida dos testes.

Os benefícios principais incluem:

  • Isolamento total: cada classe ou suíte de testes ganha seu próprio container, eliminando conflitos de estado
  • Fidelidade ao ambiente real: usa exatamente a mesma imagem do banco/fila que você usa em produção
  • Zero configuração manual: o container é iniciado com parâmetros programáticos — sem arquivos docker-compose para cada cenário
  • Limpeza automática: containers são encerrados e removidos após os testes via Gancho JUnit/@AfterEach
  • Suporte a módulos especializados: PostgreSQL, MySQL, MongoDB, Kafka, LocalStack (AWS), Toxiproxy (chaos), e dezenas de outros

Exemplo Prático com Java e Spring Boot

Vamos construir um teste de integração para um repositório Spring Data JPA que consulta usuários por email. Sem Testcontainers, você dependeria de um banco H2 em memória — que tem diferenças sutis de SQL em relação ao PostgreSQL de produção.

Primeiro, adicione a dependência no pom.xml:

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers-bom</artifactId>
    <version>1.20.4</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>

Agora, o teste de integração propriamente dito:

@SpringBootTest
@Testcontainers
class UsuarioRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16-alpine")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void configure(DynamicPropertyRegistry reg) {
        reg.add("spring.datasource.url", postgres::getJdbcUrl);
        reg.add("spring.datasource.username", postgres::getUsername);
        reg.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private UsuarioRepository repository;

    @Test
    void deveEncontrarUsuarioPorEmail() {
        Usuario usuario = new Usuario("João", "joao@email.com");
        repository.save(usuario);

        Optional<Usuario> encontrado =
            repository.findByEmail("joao@email.com");

        assertThat(encontrado).isPresent();
        assertThat(encontrado.get().getNome()).isEqualTo("João");
    }
}

Perceba os detalhes importantes: a anotação @Testcontainers gerencia o ciclo de vida, @Container marca o container que será iniciado antes dos testes, e @DynamicPropertySource injeta dinamicamente as credenciais no contexto Spring. Quando o teste termina, o container PostgreSQL é automaticamente destruído.

Testcontainers com Python e Pytest

Para o ecossistema Python, a biblioteca testcontainers oferece a mesma experiência. Veja um exemplo testando uma função que interage com Redis:

from testcontainers.redis import RedisContainer
import redis

def test_cache_function():
    with RedisContainer("redis:7-alpine") as redis_container:
        # Conecta ao Redis real dentro do container
        client = redis.Redis(
            host=redis_container.get_container_host_ip(),
            port=redis_container.get_exposed_port(6379)
        )

        # Executa a lógica de negócio
        client.set("chave", "valor")
        resultado = client.get("chave")

        assert resultado == b"valor"

O with statement garante que o container seja iniciado antes e encerrado depois — mesmo que o teste lance uma exceção. Para cenários mais complexos, como múltiplos containers simultâneos, o Docker Compose via Testcontainers também é suportado:

from testcontainers.compose import DockerCompose
import psycopg2

def test_com_integracao_real():
    compose = DockerCompose("./docker-config")
    with compose:
        # Aguarda serviços ficarem saudáveis
        compose.wait_for(service="postgres", port=5432)

        conn = psycopg2.connect(
            host="localhost",
            port=compose.get_service_port("postgres", 5432),
            database="meudb",
            user="admin",
            password="admin"
        )
        cur = conn.cursor()
        cur.execute("SELECT COUNT(*) FROM usuarios")
        assert cur.fetchone()[0] == 0

Módulos Especializados que Valem Ouro

O ecossistema Testcontainers vai muito além de bancos de dados relacionais. Alguns módulos que resolvem dores reais do dia a dia:

  • LocalStack: emula serviços AWS (S3, SQS, DynamoDB, Lambda) localmente — perfeito para testar integrações com a nuvem sem custos
  • Kafka / Redpanda: teste produtores e consumidores de eventos sem depender de um cluster remoto
  • Toxiproxy: simule latência, desconexões e degradação de rede para testar resiliência (timeouts, retries, circuit breakers)
  • Elasticsearch / OpenSearch: valide queries de busca full-text em um ambiente real
  • MongoDB: teste agregações e índices sem usar a biblioteca em-memory (que tem comportamento diferente)

Boas Práticas e Pitfalls

Após usar Testcontainers em diversos projetos, alguns aprendizados são valiosos:

  1. Reutilize containers quando apropriado: containers PostgreSQL podem ser compartilhados entre testes da mesma classe com @Container(shared = true), reduzindo o tempo total da suíte. Use @TestInstance(TestInstance.Lifecycle.PER_CLASS) para manter o container vivo entre métodos.
  2. Não compartilhe containers entre classes diferentes: cada classe de teste deve ter seu próprio container ou usar um container único com limpeza de dados entre classes. Compartilhar sem isolamento leva a testes que passam na sua máquina e quebram no CI.
  3. Configure timeouts adequados: em ambientes CI, o pull de imagens pode ser lento. Defina um timeout global (ryuk.container.timeout) e considere pré-baixar imagens no setup do pipeline.
  4. Use imagens Alpine: versões -alpine reduzem drasticamente o tempo de download e o consumo de recursos — especialmente importante em CI com paralelismo.
  5. Cuidado com portas fixas: sempre use portas mapeadas dinamicamente (padrão do Testcontainers) em vez de fixar portas como 5432. Portas fixas causam conflitos quando múltiplos testes rodam em paralelo.

Integração com CI/CD

Testcontainers funciona perfeitamente em pipelines CI/CD — desde que o ambiente tenha o Docker disponível. No GitHub Actions, por exemplo, o Docker já vem instalado nos runners ubuntu-latest. Basta garantir que o serviço Docker esteja rodando:

# .github/workflows/test.yml
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      docker:
        image: docker:27-dind
        options: --privileged

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - run: mvn test -pl :meu-servico
      # Testcontainers gerencia os containers automaticamente

No GitLab CI, é necessário configurar o Docker-in-Docker (dind) ou usar runners com socket bind:

variables:
  DOCKER_HOST: tcp://docker:2375
  DOCKER_TLS_CERTDIR: ""

services:
  - docker:27-dind

Conclusão

Testcontainers elimina o abismo entre testes de unidade e testes de produção. Com ele, você testa contra instâncias reais de bancos de dados, filas e serviços de nuvem — sem mocks frágeis, sem configuração manual de infraestrutura, e com a garantia de que cada execução começa de um estado limpo e conhecido.

A abordagem eleva a confiança nos testes de integração a um nível que mocks tradicionais simplesmente não alcançam. Se você ainda não adotou Testcontainers no seu projeto, comece por um único módulo — o ganho em confiabilidade e a redução de bugs em produção vão te convencer em poucos dias.

Craft XP
Craft XP