Introdução: Por que testar em Go é fundamental
Go (Golang) foi criado com foco em simplicidade e confiabilidade. O pacote embutido testing é uma das melhores ferramentas para escrever testes entre as linguagens modernas. Ele não requer bibliotecas de terceiros para cenários básicos, mas é facilmente extensível. Neste artigo, vamos abordar três níveis de teste: unit, integration e e2e. Você aprenderá como escrevê-los, quando aplicá-los e quais ferramentas usar. O material será útil tanto para iniciantes quanto para desenvolvedores Go experientes.
1. Testes unitários em Go: o básico dos básicos
Testes unitários verificam funções ou métodos individuais isoladamente. Em Go, basta criar um arquivo com o sufixo _test.go e escrever funções no formato TestXxx(t *testing.T).
Exemplo simples de teste unitário
Suponha que temos uma função de soma:
// math.gopackage main
func Add(a, b int) int { return a + b}O teste para ela:
// math_test.gopackage main
import "testing"
func TestAdd(t *testing.T) { result := Add(2, 3) expected := 5 if result != expected { t.Errorf("Add(2, 3) = %d; want %d", result, expected) }}Testes orientados a tabela (Table-Driven Tests)
A comunidade Go recomenda usar testes orientados a tabela para verificar múltiplos casos:
func TestAddTable(t *testing.T) { tests := []struct { name string a, b int want int }{ {"positive", 2, 3, 5}, {"negative", -1, -2, -3}, {"zero", 0, 0, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Add(tt.a, tt.b); got != tt.want { t.Errorf("Add() = %d, want %d", got, tt.want) } }) }}Os testes unitários são executados com o comando:
go test ./...2. Testes de integração: verificando a interação
Testes de integração verificam como diferentes partes do sistema funcionam juntas: banco de dados, API, sistema de arquivos. Em Go, é comum colocá-los em arquivos separados com uma tag de compilação ou no pacote integration.
Usando build tags
Crie um arquivo integration_test.go com a tag:
//go:build integration
package integration
import ( "database/sql" "testing" _ "github.com/lib/pq")
func TestDatabaseConnection(t *testing.T) { dsn := "host=localhost port=5432 user=test dbname=test sslmode=disable" db, err := sql.Open("postgres", dsn) if err != nil { t.Fatalf("Failed to open DB: %v", err) } defer db.Close()
err = db.Ping() if err != nil { t.Fatalf("Failed to ping DB: %v", err) }}Executando apenas os testes de integração:
go test -tags=integration ./...Trabalhando com banco de dados de teste
Para testes de integração, frequentemente se usa testcontainers-go ou bancos de dados in-memory (ex.: SQLite). Exemplo com testcontainers:
func TestWithPostgresContainer(t *testing.T) { ctx := context.Background() pgContainer, err := postgres.RunContainer(ctx, testcontainers.WithImage("postgres:15"), postgres.WithDatabase("testdb"), ) require.NoError(t, err) defer pgContainer.Terminate(ctx)
dsn, _ := pgContainer.ConnectionString(ctx) // usamos dsn para conectar}3. Testes E2E: verificando o sistema como um todo
Testes End-to-End simulam o comportamento do usuário e verificam todo o sistema do início ao fim. Em Go, eles são frequentemente escritos para serviços HTTP usando o pacote net/http/httptest.
Exemplo de teste E2E para REST API
func TestCreateUserE2E(t *testing.T) { // C