Go Test: Como Testar em Go com testing, -race e Cobertura
Resposta rápida: para rodar todos os testes de um projeto Go, execute go test ./... na raiz do módulo. Crie os casos em arquivos *_test.go, com funções como TestSomar(t *testing.T). Use -v para detalhes, -run para filtrar, -count=1 para ignorar o cache, -race para detectar corridas observadas e -cover para medir cobertura. Você não precisa de framework: o pacote nativo testing já inclui subtests, benchmarks e fuzzing, enquanto net/http/httptest cobre handlers e clientes HTTP.
Se a sua dúvida é como usar go test, como fazer testes em Golang ou o que o pacote testing oferece, siga esta ordem: primeiro faça o teste rodar; depois organize os casos em tabela, isole dependências, teste HTTP, verifique concorrência e leve a suíte para o CI.
Como usar go test: decisão rápida
| Se você quer… | Rode… | O que acontece |
|---|---|---|
| Testar o pacote atual | go test | Compila e executa os testes do diretório |
| Rodar todos os testes do módulo | go test ./... | Inclui o pacote atual e todos os subpacotes |
| Ver nomes e logs dos testes | go test -v ./... | Ativa a saída detalhada |
| Rodar apenas um teste | go test -run '^TestSomar$' ./... | Filtra o nome por expressão regular |
| Reexecutar sem cache | go test -count=1 ./... | Ignora um resultado bem-sucedido armazenado |
| Procurar data races | go test -race ./... | Instrumenta os caminhos concorrentes executados |
| Medir cobertura | go test -cover ./... | Exibe a porcentagem de statements executados |
| Rodar benchmarks | go test -bench=. -benchmem ./... | Mede tempo e alocações dos benchmarks |
| Iniciar fuzzing | go test -fuzz=FuzzNome | Muta entradas para procurar falhas |
Na maioria dos repositórios, o comando diário é go test ./.... No CI, combine-o com go vet ./...; adicione -race em um job próprio quando o custo for alto. Se você ainda está montando o módulo, comece por Go Modules na prática e volte aqui com um pacote que já compila.
O essencial do comando go test
A ferramenta go test compila o pacote, cria um binário de teste temporário, executa as funções reconhecidas pelo pacote testing e retorna código diferente de zero quando algo falha. O sufixo ./... significa “este pacote e todos os pacotes abaixo dele”.
| Objetivo | Comando |
|---|---|
| Testar o pacote atual | go test |
| Testar todo o módulo | go test ./... |
| Mostrar cada teste e log | go test -v ./... |
| Rodar um teste específico | go test -run '^TestSomar$' ./... |
| Rodar um subtest | go test -run 'TestSomar/negativos' |
| Ignorar resultado em cache | go test -count=1 ./... |
| Parar após um tempo | go test -timeout=30s ./... |
| Detectar corridas observadas | go test -race ./... |
| Medir cobertura | go test -cover ./... |
| Rodar benchmarks | go test -bench=. -benchmem ./... |
Por padrão, resultados bem-sucedidos podem ser reutilizados do cache. Isso acelera o desenvolvimento; -count=1 é útil quando você precisa confirmar que um teste instável realmente executou de novo. Para descobrir flags aceitas pelo binário de teste, use go help testflag.
Primeiro teste unitário em Go
Crie a função de produção:
// calculadora.go
package calculadora
func Somar(a, b int) int {
return a + b
}
No mesmo pacote, crie um arquivo terminado em _test.go:
// calculadora_test.go
package calculadora
import "testing"
func TestSomar(t *testing.T) {
obtido := Somar(2, 3)
esperado := 5
if obtido != esperado {
t.Fatalf("Somar(2, 3) = %d; esperado %d", obtido, esperado)
}
}
Rode:
go test
O nome precisa começar com Test seguido por letra maiúscula ou caractere que não seja minúsculo. t.Errorf registra a falha e deixa o teste continuar; t.Fatalf registra e encerra aquele teste imediatamente. Prefira mensagens com entrada, resultado obtido e resultado esperado, porque elas reduzem o tempo de diagnóstico no CI.
Você pode declarar o arquivo como package calculadora_test para testar somente a API pública. Usar o mesmo pacote permite acessar detalhes não exportados. Não existe uma escolha universal: testes externos protegem melhor o contrato; testes internos são úteis para algoritmos e componentes que precisam de diagnóstico mais fino.
Table-driven tests e subtests
O padrão mais comum na comunidade Go é colocar cenários em uma tabela e executá-los com t.Run:
func TestSomar(t *testing.T) {
testes := []struct {
nome string
a, b int
esperado int
}{
{nome: "positivos", a: 2, b: 3, esperado: 5},
{nome: "negativos", a: -2, b: -3, esperado: -5},
{nome: "sinais diferentes", a: -2, b: 5, esperado: 3},
{nome: "zeros", a: 0, b: 0, esperado: 0},
}
for _, tt := range testes {
t.Run(tt.nome, func(t *testing.T) {
obtido := Somar(tt.a, tt.b)
if obtido != tt.esperado {
t.Errorf("Somar(%d, %d) = %d; esperado %d",
tt.a, tt.b, obtido, tt.esperado)
}
})
}
}
Além de reduzir repetição, cada linha aparece separadamente no modo verbose e pode ser selecionada por -run. Casos de borda ficam visíveis ao lado dos casos felizes. Para padrões de tabela mais avançados — erros esperados, funções de setup e comparações — veja o guia de table-driven tests em Go.
Setup, cleanup e arquivos temporários
Evite deixar estado global ou arquivos depois do teste. t.Cleanup registra uma função executada ao final, mesmo quando o teste falha. t.TempDir cria um diretório exclusivo e remove tudo automaticamente:
func TestSalvar(t *testing.T) {
dir := t.TempDir()
arquivo := filepath.Join(dir, "config.json")
if err := Salvar(arquivo, []byte(`{"porta":8080}`)); err != nil {
t.Fatalf("Salvar() retornou erro: %v", err)
}
conteudo, err := os.ReadFile(arquivo)
if err != nil {
t.Fatalf("ReadFile() retornou erro: %v", err)
}
if string(conteudo) != `{"porta":8080}` {
t.Errorf("conteúdo = %q", conteudo)
}
}
Para valores exigidos por código que lê ambiente, use t.Setenv("APP_ENV", "test"); o pacote restaura o valor depois. Para fixtures versionadas, coloque arquivos em testdata/: o toolchain ignora esse diretório ao descobrir pacotes, mas seu teste pode lê-lo normalmente. Arquivos pequenos também podem ser incorporados com go:embed.
TestMain(m *testing.M) existe para setup global do pacote, mas use com parcimônia. Banco, servidor e estado compartilhados tornam a suíte mais acoplada. Quando possível, inicialize recursos no teste ou em um helper e limpe com t.Cleanup.
Testando erros sem comparar texto frágil
Se o contrato exporta um erro sentinela, verifique com errors.Is:
func TestBuscarUsuarioInexistente(t *testing.T) {
_, err := BuscarUsuario(42)
if !errors.Is(err, ErrUsuarioNaoEncontrado) {
t.Fatalf("erro = %v; esperado ErrUsuarioNaoEncontrado", err)
}
}
Comparar err.Error() inteiro quebra quando uma camada adiciona contexto com %w. Para tipos de erro, use errors.As. Esse estilo mantém o teste ligado ao comportamento, não à pontuação da mensagem. O guia de erros em Go aprofunda wrapping, sentinelas e contratos.
Fakes, mocks e dependency injection
O teste unitário mais simples não acessa rede, relógio ou banco real. Coloque essas fronteiras atrás de interfaces pequenas e forneça um fake:
type RepositorioUsuarios interface {
Buscar(id int) (Usuario, error)
}
type repositorioFake struct {
usuarios map[int]Usuario
}
func (r repositorioFake) Buscar(id int) (Usuario, error) {
usuario, ok := r.usuarios[id]
if !ok {
return Usuario{}, ErrUsuarioNaoEncontrado
}
return usuario, nil
}
func TestServicoBuscar(t *testing.T) {
repo := repositorioFake{
usuarios: map[int]Usuario{1: {ID: 1, Nome: "Ana"}},
}
servico := NovoServico(repo)
usuario, err := servico.Buscar(1)
if err != nil {
t.Fatalf("Buscar() retornou erro: %v", err)
}
if usuario.Nome != "Ana" {
t.Errorf("Nome = %q; esperado Ana", usuario.Nome)
}
}
Em Go, fakes escritos à mão frequentemente são mais claros que um framework de mock. Testify, GoMock e geradores ajudam quando é importante verificar muitas interações, mas podem acoplar o teste à ordem interna das chamadas. Compare as opções no guia de mocks, fakes, Testify e GoMock e veja como aplicar dependency injection sem framework.
Testando handlers HTTP com httptest
net/http/httptest permite chamar um handler sem abrir uma porta:
func TestHealth(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/health", nil)
rec := httptest.NewRecorder()
healthHandler(rec, req)
res := rec.Result()
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Fatalf("status = %d; esperado %d", res.StatusCode, http.StatusOK)
}
if tipo := res.Header.Get("Content-Type"); tipo != "application/json" {
t.Errorf("Content-Type = %q", tipo)
}
}
Use httptest.NewServer quando o componente testado exige um http.Client e uma URL real. Aponte o cliente para server.URL, em vez de chamar um serviço externo. Isso deixa o teste rápido e determinístico. O tutorial de API REST com Go mostra como organizar handlers para que esse tipo de teste seja natural.
Testes de integração com banco e containers
Nem tudo deve ser falso. Queries SQL, migrations, serialização e contratos com infraestrutura precisam de uma camada de integração. Separe-a por pacote, diretório ou build tag:
//go:build integration
package postgres_test
func TestRepositorioPostgres(t *testing.T) {
// inicia banco isolado, aplica migration e verifica a query real
}
Execute explicitamente:
go test -tags=integration ./...
Para subir PostgreSQL, Redis ou outro serviço durante a suíte, leia Testcontainers em Go. A divisão saudável costuma ser: muitos testes unitários rápidos, alguns testes de integração nas fronteiras e poucos testes ponta a ponta para fluxos críticos.
Race detector e testes concorrentes
go test -race ./... instrumenta o programa e acusa acessos concorrentes incompatíveis que realmente aconteceram durante a execução. Ele é essencial para código com goroutines, channels, caches e mapas compartilhados, mas não prova ausência de corrida: caminhos não exercitados continuam invisíveis.
Para testes paralelos independentes, chame t.Parallel() no início do subtest. Não paralelize testes que compartilham variável global, porta fixa ou banco sem isolamento. Se um teste concorrente depende de time.Sleep, ele tende a ser lento e instável; prefira sincronização observável por channel, contexto ou sinais controlados. Para fundamentos, consulte concorrência em Go e a cheatsheet de concorrência.
Cobertura: como medir e como interpretar
Gere um perfil e abra a visualização por linha:
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out
Cobertura responde “quais instruções foram executadas?”, não “as verificações estavam corretas?”. Um teste que chama uma função sem conferir o resultado aumenta a porcentagem e não protege comportamento. Use o relatório para encontrar regras de negócio, erros e branches relevantes sem teste. Evite perseguir 100% em getters, código gerado ou caminhos impossíveis apenas para melhorar o número.
Quando vários pacotes participam do fluxo, -coverpkg=./... amplia os pacotes instrumentados:
go test -coverpkg=./... -coverprofile=coverage.out ./...
Defina um limite no CI somente quando o time entende o baseline e sabe que tipo de risco ele representa. Uma queda inesperada pode ser útil; uma meta universal de porcentagem não é garantia de qualidade.
Benchmarks com testing.B
Benchmarks também ficam em arquivos _test.go. Nas versões atuais do Go, b.Loop executa o corpo pela quantidade necessária de iterações:
func BenchmarkNormalizar(b *testing.B) {
entrada := strings.Repeat(" Go ", 100)
for b.Loop() {
_ = Normalizar(entrada)
}
}
Execute:
go test -bench=BenchmarkNormalizar -benchmem -count=5
-benchmem mostra alocações; -count=5 produz várias amostras para comparação. Mantenha dados de entrada equivalentes em cada iteração. Um benchmark de ordenação, por exemplo, precisa copiar a slice antes de ordenar — medir repetidamente uma slice que já ficou ordenada distorce o resultado. Para otimização baseada em evidência, combine benchmark com pprof em Go e evite otimizar apenas porque um número isolado parece grande.
Fuzz testing nativo
Fuzzing gera e muta entradas para encontrar panics e violações de propriedades. Um alvo começa com Fuzz e recebe seeds conhecidos:
func FuzzDecodificar(f *testing.F) {
f.Add([]byte(`{"id":1}`))
f.Add([]byte(`{}`))
f.Fuzz(func(t *testing.T, dados []byte) {
_, _ = Decodificar(dados) // não deve causar panic
})
}
Rode a exploração por um intervalo controlado:
go test -fuzz=^FuzzDecodificar$ -fuzztime=30s
Entradas que causam falha são gravadas para regressão. Bons alvos incluem parsers, decoders, validadores, formatos binários e funções com propriedades de round-trip. Veja o guia completo de fuzz testing com testing.F.
Pipeline mínimo de testes no CI
Uma rotina segura começa simples:
gofmt -w .
go vet ./...
go test ./...
go test -race ./...
No CI, em vez de executar gofmt -w e alterar arquivos, falhe quando houver diff. Acrescente análise estática com Staticcheck e golangci-lint e vulnerabilidades com govulncheck. Testes de integração podem rodar em job separado para preservar feedback rápido.
A ordem prática é:
- formatação e compilação;
- unit tests de todos os pacotes;
- análise estática;
- race detector onde há concorrência;
- integração com serviços isolados;
- build do binário ou imagem.
O tutorial de testes em Go com TDD e CI/CD inclui um workflow de GitHub Actions completo.
Erros comuns em suítes Go
- Usar
time.Sleepcomo sincronização: o teste fica lento e falha sob carga. - Compartilhar estado entre testes: a ordem de execução passa a alterar o resultado.
- Testar implementação, não comportamento: qualquer refatoração quebra dezenas de mocks.
- Chamar APIs reais em unit tests: rede, credenciais e rate limits tornam o resultado imprevisível.
- Ignorar o erro no próprio teste: o teste pode validar um valor inválido depois de uma falha anterior.
- Comparar mensagem completa de erro: wrapping ou contexto legítimo quebra a suíte.
- Confundir cobertura com qualidade: linhas executadas sem assertions não protegem o contrato.
- Deixar teste instável em retry permanente: flakiness é defeito de produto ou de teste e precisa de causa identificada.
Checklist para testar um projeto Go
-
go test ./...passa localmente e no CI. - Casos felizes, erros e limites relevantes estão cobertos.
- Casos repetitivos usam table-driven tests e subtests.
- Arquivos e ambiente usam
t.TempDir,t.Setenvet.Cleanup. - HTTP externo é substituído por
httptestnos unit tests. - Banco e filas reais são cobertos por testes de integração isolados.
- Código concorrente roda regularmente com
-race. - Cobertura é revisada por risco, não apenas por porcentagem.
- Benchmarks recriam entradas mutáveis e usam múltiplas amostras.
- Fuzzing protege parsers e fronteiras expostas a entradas arbitrárias.
Perguntas frequentes
Como rodar todos os testes em Go?
Use go test ./... na raiz do módulo. O ./... inclui o pacote atual e os subpacotes. Adicione -v para saída detalhada e -count=1 para executar sem reaproveitar um sucesso em cache.
Go precisa de framework para testes unitários?
Não. testing, httptest, cobertura, benchmarks e fuzzing fazem parte da distribuição do Go. Frameworks são opcionais; adote um quando ele reduz complexidade real, não apenas para imitar outra linguagem.
Qual é a diferença entre mock e fake?
Um fake é uma implementação funcional e simplificada, como um repositório em memória. Um mock normalmente registra expectativas sobre chamadas, argumentos e quantidade. Fakes tendem a testar comportamento com menos acoplamento; mocks são úteis quando a interação em si faz parte do contrato.
Quando usar go test -race?
Sempre que o projeto tiver concorrência relevante e regularmente no CI. Como a execução é mais cara, repositórios grandes podem colocá-lo em job separado. Lembre que ele só encontra corridas nos caminhos exercitados.
Quanto de cobertura um projeto Go deve ter?
Não existe porcentagem universal. Priorize regras de negócio, autorização, dinheiro, persistência, concorrência e tratamento de erros. Use a tendência de cobertura para detectar regressões, mas revise também a qualidade das assertions e os cenários ausentes.
Próximos passos
- Rode
go test ./...no seu projeto. - Converta casos repetidos para table-driven tests.
- Teste handlers com
httptestseguindo o tutorial de API REST com Go. - Adicione
-race, cobertura e análise estática ao CI. - Use Testcontainers para validar fronteiras reais.
Última atualização: Agosto de 2026 — resposta direta para go test e golang testing, comandos de execução, table-driven tests, cleanup, mocks e fakes, httptest, integração, race detector, cobertura, benchmarks com b.Loop, fuzzing e CI.