Introdução: Por que testar em Go é uma necessidade?
\nGo (Golang) é uma linguagem de programação moderna, criada para construir sistemas confiáveis e escaláveis. No entanto, mesmo o código mais elegante não está imune a erros. É por isso que os testes são uma parte essencial do desenvolvimento em Go. O pacote embutido testing fornece um conjunto poderoso de ferramentas para escrever testes unitários, testes de integração e até mesmo verificações end-to-end (e2e).
Neste artigo, abordaremos todos os níveis de teste em Go: desde testes unitários simples até cenários e2e complexos. Você aprenderá como estruturar testes corretamente, usar mocks, escrever table-driven tests e configurar testes de integração com dependências reais. O material será útil tanto para iniciantes quanto para desenvolvedores experientes que desejam sistematizar seus conhecimentos.
\n\n1. Testes unitários em Go: o básico
\nTestes unitários verificam o funcionamento de funções ou métodos individuais isolados de dependências externas. Em Go, usa-se o pacote padrão testing. As regras principais: os arquivos de teste devem ser nomeados *_test.go, e as funções de teste devem começar com Test.
1.1. Teste unitário simples
\nVamos considerar uma função de soma de dois números:
\n// math.go\npackage math\n\nfunc Add(a, b int) int {\n return a + b\n}\nTeste para ela:
\n// math_test.go\npackage math\n\nimport "testing"\n\nfunc TestAdd(t *testing.T) {\n result := Add(2, 3)\n expected := 5\n if result != expected {\n t.Errorf("Add(2, 3) = %d; want %d", result, expected)\n }\n}\nExecução dos testes: go test ./... ou go test -v para saída detalhada.
1.2. Table-driven tests (testes orientados por tabela)
\nA comunidade Go usa ativamente a abordagem table-driven para testar vários casos:
\nfunc TestAddTableDriven(t *testing.T) {\n tests := []struct {\n name string\n a, b int\n want int\n }{\n {"positive", 2, 3, 5},\n {"negative", -1, -2, -3},\n {"zero", 0, 0, 0},\n }\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n if got := Add(tt.a, tt.b); got != tt.want {\n t.Errorf("Add() = %d, want %d", got, tt.want)\n }\n })\n }\n}\nEssa abordagem torna os testes fáceis de ler e expandir.
\n\n1.3. Testes com erros
\nPara funções que retornam erros, use t.Fatal ou t.Errorf:
func TestDivide(t *testing.T) {\n _, err := Divide(10, 0)\n if err == nil {\n t.Fatal("expected error for division by zero")\n }\n}\n\n2. Testes de integração: trabalhando com dependências
\nTestes de integração verificam a interação entre componentes: banco de dados, API, sistema de arquivos. Em Go, é comum separá-los dos testes unitários usando build tags ou uma estrutura de pacotes separada.
\n\n2.1. Uso de build tags
\nCrie um arquivo com a tag //go:build integration no início:
//go:build integration\n\npackage db\n\nimport (\n "testing"\n "database/sql"\n _ "github.com/lib/pq"\n)\n\nfunc TestPostgresConnection(t *testing.T) {\n dsn := "host=localhost port=5432 user=test password=test dbname=test sslmode=disable"\n db, err := sql.Open("postgres", dsn)\n if err != nil {\n t.Fatalf("failed to open db: %v", err)\n }\n defer db.Close()\n \n if err := db.Ping(); err != nil {\n t.Fatalf("failed to ping db: %v", err)\n }\n}\nExecução apenas dos testes de integração: go test -tags=integration ./...
2.2. Mocks e stubs
\nPara isolar testes, use interfaces e mocks. Exemplo com a biblioteca popular gomock:
// user.go\ntype UserRepository interface {\n GetUser(id int) (*Us