Em muitas linguagens, testar código exige escolher entre uma dúzia de frameworks, aprender suas APIs e configurar um ambiente separado. Em Go, testes são parte da linguagem. O pacote testing está na biblioteca padrão, o comando go test está no toolchain e a convenção de nomes é simples e universalmente adotada.
Não existe uma razão legítima para escrever código Go sem testes. O custo de entrada é mínimo e os benefícios — confiança nas mudanças, documentação executável, detecção precoce de regressões — são imensos.
Anatomia de um teste
Todo arquivo de teste em Go segue três regras:
- O nome do arquivo termina com
_test.go - O pacote é o mesmo do código testado (ou com sufixo
_testpara testes externos) - Cada função de teste começa com
Teste recebe*testing.Tcomo único parâmetro
// arquivo: calculadora/calculadora.go
package calculadora
func Somar(a, b float64) float64 {
return a + b
}
func Dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("divisão por zero")
}
return a / b, nil
}
// arquivo: calculadora/calculadora_test.go
package calculadora
import (
"testing"
)
func TestSomar(t *testing.T) {
resultado := Somar(2, 3)
esperado := 5.0
if resultado != esperado {
t.Errorf("Somar(2, 3) = %.1f; esperado %.1f", resultado, esperado)
}
}
Para executar:
go test ./... # todos os pacotes
go test ./calculadora/ # pacote específico
go test -v ./calculadora/ # verbose — mostra cada teste
go test -run TestSomar # executa apenas testes que correspondem ao padrão
Os métodos de *testing.T
O tipo *testing.T oferece um conjunto de métodos para reportar falhas e controlar a execução:
func TestMetodos(t *testing.T) {
// Reporta falha mas continua o teste
t.Error("algo deu errado")
t.Errorf("valor obtido: %d, esperado: %d", 1, 2)
// Reporta falha e para o teste imediatamente
t.Fatal("falha irrecuperável")
t.Fatalf("não foi possível abrir arquivo: %v", err)
// Marca o teste como ignorado
t.Skip("pulando — requer banco de dados")
t.Skipf("pulando na versão %s", runtime.Version())
// Log — visível apenas com -v ou quando o teste falha
t.Log("contexto adicional")
t.Logf("estado atual: %+v", estrutura)
}
A diferença entre Error e Fatal é importante: Error registra a falha e continua executando o restante do teste — útil para verificar múltiplas condições. Fatal para o teste imediatamente — útil quando as verificações seguintes dependem do estado atual e não fazem sentido se ele for inválido.
Tabela de casos: o padrão mais importante
O padrão de table-driven tests é o idioma central de testes em Go. Em vez de escrever uma função de teste para cada caso, define-se uma tabela com todos os casos e itera-se sobre ela:
package calculadora
import (
"math"
"testing"
)
func TestDividir(t *testing.T) {
casos := []struct {
nome string
a, b float64
esperado float64
erroEsperado bool
}{
{"divisão simples", 10, 2, 5.0, false},
{"divisão com decimal", 7, 2, 3.5, false},
{"dividendo zero", 0, 5, 0.0, false},
{"divisão por zero", 10, 0, 0.0, true},
{"negativos", -10, -2, 5.0, false},
{"resultado negativo", 10, -2, -5.0, false},
}
for _, tc := range casos {
tc := tc // captura de variável de laço — necessário antes do Go 1.22
t.Run(tc.nome, func(t *testing.T) {
resultado, err := Dividir(tc.a, tc.b)
if tc.erroEsperado {
if err == nil {
t.Error("esperava erro, mas não recebeu nenhum")
}
return
}
if err != nil {
t.Fatalf("erro inesperado: %v", err)
}
if math.Abs(resultado-tc.esperado) > 1e-9 {
t.Errorf("Dividir(%.1f, %.1f) = %.4f; esperado %.4f",
tc.a, tc.b, resultado, tc.esperado)
}
})
}
}
t.Run cria subtestes nomeados. Cada caso aparece separadamente na saída com -v, pode ser executado individualmente com -run TestDividir/divisão_simples e falhas em um caso não impedem os demais de executar.
Helpers de teste
Funções auxiliares reutilizadas por múltiplos testes são comuns. O método t.Helper() marca a função como helper — quando ela reporta uma falha, a linha apontada no erro é a do chamador, não a do helper:
package calculadora
import "testing"
func assertEqual(t *testing.T, obtido, esperado float64) {
t.Helper() // essencial para mensagens de erro úteis
if obtido != esperado {
t.Errorf("obtido %.4f; esperado %.4f", obtido, esperado)
}
}
func assertErro(t *testing.T, err error) {
t.Helper()
if err == nil {
t.Error("esperava um erro, mas não recebeu nenhum")
}
}
func assertSemErro(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("erro inesperado: %v", err)
}
}
func TestSomarComHelpers(t *testing.T) {
assertEqual(t, Somar(2, 3), 5.0)
assertEqual(t, Somar(-1, 1), 0.0)
assertEqual(t, Somar(0, 0), 0.0)
}
Setup e Teardown com TestMain
Quando testes precisam de setup global — como inicializar um banco de dados de teste ou criar arquivos temporários — TestMain permite executar código antes e depois de todos os testes do pacote:
package calculadora
import (
"fmt"
"os"
"testing"
)
func TestMain(m *testing.M) {
// Setup — executado antes de todos os testes
fmt.Println("Inicializando ambiente de teste...")
// m.Run() executa todos os testes e retorna o código de saída
codigo := m.Run()
// Teardown — executado após todos os testes
fmt.Println("Limpando ambiente de teste...")
os.Exit(codigo)
}
Para setup e teardown por teste individual, use funções auxiliares chamadas no início e defer para limpeza:
func configurarTeste(t *testing.T) func() {
t.Helper()
// setup
dir, err := os.MkdirTemp("", "teste-*")
if err != nil {
t.Fatal(err)
}
// retorna função de teardown
return func() {
os.RemoveAll(dir)
}
}
func TestComArquivos(t *testing.T) {
teardown := configurarTeste(t)
defer teardown()
// ... teste usando os arquivos temporários
}
Benchmarks: medindo performance
Funções de benchmark seguem a mesma convenção dos testes, com Benchmark no lugar de Test e *testing.B como parâmetro:
package calculadora
import "testing"
func BenchmarkSomar(b *testing.B) {
for i := 0; i < b.N; i++ {
Somar(42.0, 58.0)
}
}
func BenchmarkDividir(b *testing.B) {
for i := 0; i < b.N; i++ {
Dividir(100.0, 3.0)
}
}
b.N é ajustado automaticamente pelo framework até que o resultado seja estatisticamente confiável. Para executar benchmarks:
go test -bench=. # todos os benchmarks
go test -bench=BenchmarkSomar # benchmark específico
go test -bench=. -benchmem # inclui alocações de memória
go test -bench=. -count=5 # executa 5 vezes para estabilidade
go test -bench=. -benchtime=3s # executa por 3 segundos em vez do padrão
Saída típica:
BenchmarkSomar-8 1000000000 0.3125 ns/op
BenchmarkDividir-8 756123456 1.584 ns/op 0 B/op 0 allocs/op
Os campos significam: nome do benchmark, número de iterações, tempo por operação, bytes alocados por operação e número de alocações por operação.
Benchmarks com reset e pause
Quando o setup do benchmark é custoso e não deve ser medido, b.ResetTimer zera o timer após o setup:
func BenchmarkProcessarSliceGrande(b *testing.B) {
// Setup custoso — não deve ser medido
dados := make([]int, 100000)
for i := range dados {
dados[i] = i
}
b.ResetTimer() // começa a medir aqui
for i := 0; i < b.N; i++ {
processarSlice(dados)
}
}
Para pausar e retomar o timer durante o loop:
func BenchmarkComIO(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
// preparar dados para esta iteração
dados := gerarDados()
b.StartTimer()
processar(dados)
}
}
Testes de exemplo: documentação executável
Funções de exemplo começam com Example e servem simultaneamente como documentação e como testes. O comentário // Output: define a saída esperada — se não corresponder, o teste falha:
func ExampleSomar() {
resultado := Somar(3, 4)
fmt.Println(resultado)
// Output:
// 7
}
func ExampleDividir() {
resultado, err := Dividir(10, 4)
if err != nil {
fmt.Println("Erro:", err)
return
}
fmt.Printf("%.2f\n", resultado)
// Output:
// 2.50
}
Esses exemplos aparecem automaticamente na documentação gerada pelo go doc e pelo pkg.go.dev, tornando-os a forma mais eficiente de documentar comportamento — a documentação nunca fica desatualizada porque é verificada pelo go test.
Cobertura de testes
Go possui suporte nativo para medir cobertura de código:
# Executar testes com cobertura
go test -cover ./...
# Gerar arquivo de cobertura detalhado
go test -coverprofile=cobertura.out ./...
# Visualizar no terminal
go tool cover -func=cobertura.out
# Visualizar em HTML — abre no navegador
go tool cover -html=cobertura.out
A visualização HTML colore cada linha: verde para linhas executadas durante os testes, vermelho para linhas não cobertas. É a forma mais rápida de identificar cenários não testados.
Testes paralelos
Testes que não compartilham estado podem ser executados em paralelo para reduzir o tempo total:
func TestOperacoesParalelas(t *testing.T) {
casos := []struct {
nome string
a, b float64
esp float64
}{
{"caso1", 1, 2, 3},
{"caso2", 4, 5, 9},
{"caso3", 10, 20, 30},
}
for _, tc := range casos {
tc := tc
t.Run(tc.nome, func(t *testing.T) {
t.Parallel() // este subteste pode rodar em paralelo com outros
resultado := Somar(tc.a, tc.b)
if resultado != tc.esp {
t.Errorf("obtido %.1f, esperado %.1f", resultado, tc.esp)
}
})
}
}
go test -parallel 4 ./... # até 4 testes em paralelo
O detector de race conditions
Go possui um detector de race conditions integrado que instrumenta o binário para detectar acessos concorrentes não sincronizados:
go test -race ./...
go run -race main.go
go build -race -o app-com-race ./
O detector tem custo em tempo de execução — cerca de 5 a 10 vezes mais lento. Use em CI/CD e em desenvolvimento, mas não em binários de produção.
Resumo do que foi coberto
Este artigo apresentou o sistema de testes do Go em profundidade: a anatomia de um arquivo de teste, os métodos de *testing.T, o padrão de tabela de casos com subtestes, helpers com t.Helper(), setup e teardown com TestMain, benchmarks com controle de timer, testes de exemplo como documentação executável, medição de cobertura, testes paralelos e o detector de race conditions. Com esse módulo completo, o curso avança para o tema mais característico do Go: concorrência.
Referências e leituras complementares
-
Documentação do pacote testing — Referência completa de T, B, F e todos os métodos. https://pkg.go.dev/testing
-
Go Blog: The Go test command — Visão geral do sistema de testes. https://go.dev/doc/code#Testing
-
Go Blog: Table Driven Tests — Artigo de Dave Cheney sobre o padrão de tabela de casos. https://dave.cheney.net/2019/05/07/prefer-table-driven-tests
-
Go by Example: Testing — Exemplos práticos comentados de testes em Go. https://gobyexample.com/testing
-
Go by Example: Benchmarks — Exemplos práticos de benchmarks. https://gobyexample.com/benchmarks
-
Documentação do go test — Todas as flags disponíveis para o comando go test. https://pkg.go.dev/cmd/go#hdr-Test_packages