Testes de Integração Go com Docker: da Teoria à Prática Avançada
\n\nTestes de integração são uma etapa crítica no desenvolvimento de microsserviços em Go. Sem verificar a interação com bancos de dados, filas de mensagens e APIs externas, você corre o risco de obter um "funciona na minha máquina" na sua manifestação mais desagradável. O Docker permite criar ambientes limpos e isolados para cada teste, e técnicas avançadas permitem fazer isso de forma rápida, paralela e sem dor. Neste artigo, abordaremos abordagens reais usadas por engenheiros seniores.
\n\n1. Por que o Docker é o parceiro ideal para testes de integração em Go
\n\nGo compila para um binário estático, tornando-o um candidato ideal para conteinerização. Em testes de integração, o Docker resolve três tarefas principais:
\n- \n
- Isolamento: cada teste recebe um banco de dados ou Redis novo, sem "lixo" de execuções anteriores. \n
- Reprodutibilidade: os testes funcionam da mesma forma na máquina do desenvolvedor, no CI/CD e em produção. \n
- Velocidade: contêineres configurados corretamente iniciam em segundos, não em minutos. \n
O erro principal de iniciantes é iniciar um único contêiner para todos os testes. Isso leva a condições de corrida e bugs "intermitentes". A abordagem avançada: um contêiner por pacote de teste ou até mesmo por teste individual.
\n\n2. Testcontainers para Go: contêineres gerenciados a partir do código
\n\nA biblioteca testcontainers-go permite gerenciar contêineres Docker diretamente dos testes Go. Isso é mais elegante do que escrever scripts bash ou depender de um Docker Compose pré-executado.
Instalação:
\ngo get github.com/testcontainers/testcontainers-go\ngo get github.com/testcontainers/testcontainers-go/modules/postgres\n\nExemplo: testando PostgreSQL com migrações:
\npackage integration_test\n\nimport (\n "context"\n "database/sql"\n "testing"\n "time"\n\n "github.com/testcontainers/testcontainers-go"\n "github.com/testcontainers/testcontainers-go/modules/postgres"\n "github.com/testcontainers/testcontainers-go/wait"\n _ "github.com/lib/pq"\n)\n\nfunc TestUserRepository(t *testing.T) {\n ctx := context.Background()\n\n pgContainer, err := postgres.RunContainer(ctx,\n testcontainers.WithImage("postgres:16-alpine"),\n postgres.WithDatabase("testdb"),\n postgres.WithUsername("user"),\n postgres.WithPassword("password"),\n testcontainers.WithWaitStrategy(\n wait.ForLog("database system is ready to accept connections").\n WithOccurrence(2).\n WithStartupTimeout(30*time.Second),\n ),\n )\n if err != nil {\n t.Fatal(err)\n }\n defer pgContainer.Terminate(ctx)\n\n connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")\n if err != nil {\n t.Fatal(err)\n }\n\n db, err := sql.Open("postgres", connStr)\n if err != nil {\n t.Fatal(err)\n }\n defer db.Close()\n\n // Aqui executamos migrações e testes\n _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (\n id SERIAL PRIMARY KEY,\n name TEXT NOT NULL\n )`)\n if err != nil {\n t.Fatal(err)\n }\n\n // Testamos o repositório\n repo := NewUserRepository(db)\n user, err := repo.Create(ctx, "Alice")\n if err != nil {\n t.Fatal(err)\n }\n if user.Name != "Alice" {\n t.Errorf("expected Alice, got %s", user.Name)\n }\n}\n\nPontos-chave:
\n- \n
WithWaitStrategy— aguardamos até que o PostgreSQL esteja realmente pronto, e não apenas o contêiner iniciado. \n defer pgContainer.Terminate(ctx)— garante a limpeza mesmo em caso de pânico. \n - Cada teste recebe sua própria instância do banco de dados — isolamento completo. \n
3. Testes paralelos e gerenciamento de recursos
\n\nGo suporta parale