Go Test e Golang Test: Como Rodar Testes em Go
Resposta rápida: para go test / golang test, execute go test ./... na raiz do módulo. Isso compila e roda todos os testes 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. Não use go run para testes: go run executa um main, enquanto go test é o comando da suíte. Você não precisa de framework — o pacote nativo testing já inclui subtests, benchmarks e fuzzing, e net/http/httptest cobre handlers HTTP.
Se a sua dúvida é como usar go test, golang test, go testing 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 que go test executa — e como diagnosticar a saída
go test compila o pacote e os arquivos *_test.go, cria um binário de teste e executa os testes reconhecidos pelo pacote testing. Uma falha de compilação, de análise automática ou de teste produz código de saída diferente de zero. Não confunda com go run: ele executa um programa main, não a suíte de testes.
Há uma diferença importante entre go test e go test .: sem argumento de pacote, o comando usa o modo de diretório local e não reaproveita resultados de testes. Com um pacote explícito, como . ou ./..., pode reutilizar resultados bem-sucedidos compatíveis com o cache. A saída (cached) indica esse reaproveitamento; use go test -count=1 ./... para forçar uma nova execução. Isso é separado do cache de compilação, que não precisa ser apagado.
Antes de executar o binário, go test também roda um subconjunto de verificações de go vet considerado de alta confiança, como problemas em chamadas de Printf. Por isso, o comando pode falhar antes de entrar em TestSomar. Rodar go vet ./... separadamente continua útil: ele aplica o conjunto padrão de verificações, não apenas esse subconjunto. Esses comportamentos estão descritos na documentação oficial de go test.
| Saída ou situação | O que verificar |
|---|---|
ok | O pacote passou; use -v para ver testes e logs |
(cached) | O sucesso foi reaproveitado; rode com -count=1 se precisar executar de novo |
[no test files] | Confira o sufixo _test.go e as build tags; esse pacote não tem arquivos de teste incluídos |
testing: warning: no tests to run | Confira a assinatura func TestNome(t *testing.T) e o filtro -run; comando bem-sucedido não garante que o teste desejado rodou |
FAIL ou erro de compilação/análise | Leia a primeira mensagem de erro antes do resumo; o problema pode ocorrer antes da execução dos testes |
-race requires cgo | Habilite cgo e confira o compilador C e a plataforma nos requisitos do race detector |
Para selecionar apenas o subtest negativos da tabela apresentada adiante, execute:
go test -v -count=1 -run '^TestSomar$/^negativos$' .
O filtro -run usa expressões regulares separadas por / para o teste e seus subtests. Os anchors ^ e $ evitam selecionar nomes parecidos. Para limitar a duração de uma suíte que pode travar, use go test -timeout=30s ./.... Consulte go help testflag ou a referência oficial de flags para os detalhes.
Primeiro teste unitário em Go
Com Go instalado, crie um diretório e inicialize um módulo de exemplo. Se você já está em um projeto com go.mod, pule esta etapa:
mkdir calculadora
cd calculadora
go mod init example.com/calculadora
Nesse diretório, 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 no diretório que contém go.mod, calculadora.go e calculadora_test.go:
go test -v ./...
A saída deve incluir === RUN TestSomar, --- PASS: TestSomar e um resumo ok para example.com/calculadora. Não é necessário criar uma função main: o comando gera o binário de teste.
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.
go test -race: exemplo, relatório e correção
Use go test -race ./... para executar a suíte com o detector de data races. Uma data race ocorre quando duas goroutines acessam a mesma variável concorrentemente, pelo menos uma escreve e os acessos não estão sincronizados. O detector instrumenta o programa e reporta conflitos observados em execução; passar no teste não prova ausência de corrida em caminhos que a suíte não exercitou. Essa definição e esse limite estão na documentação oficial do race detector.
Reproduzir uma data race sem time.Sleep
Crie um módulo separado para o experimento — não adicione um teste intencionalmente incorreto ao seu projeto:
mkdir exemplo-race
cd exemplo-race
go mod init example.com/race
Salve o arquivo completo abaixo como contador_test.go:
package contador
import (
"sync"
"testing"
)
func TestContador(t *testing.T) {
var contador int
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
contador++ // acesso compartilhado sem sincronização
}
}()
}
wg.Wait()
if contador != 2000 {
t.Fatalf("contador = %d; esperado 2000", contador)
}
}
Execute os dois comandos separadamente:
go test -count=1 .
go test -race -count=1 .
Sem -race, o teste pode passar: obter 2000 não torna o incremento seguro. Com o detector, o conflito pode aparecer como WARNING: DATA RACE e o teste falha quando a corrida é detectada. WaitGroup espera as goroutines terminarem, mas não protege os incrementos entre elas. Não use um sucesso isolado como garantia, pois a execução depende do escalonamento.
Como ler WARNING: DATA RACE
O relatório mostra as stacks dos acessos conflitantes e onde as goroutines foram criadas. Procure os blocos Read at, Write at ou Previous write at e as primeiras linhas do seu código em cada stack. Neste exemplo, eles apontam para contador++, que lê e escreve a variável compartilhada.
Corrija a sincronização, não apenas a assertion. Adicione var mu sync.Mutex junto das outras variáveis e substitua o corpo do loop interno por:
mu.Lock()
contador++
mu.Unlock()
O mesmo mutex precisa proteger todos os acessos concorrentes a esse contador. Aqui, a leitura final acontece depois de wg.Wait(), quando os incrementos já terminaram. Rode novamente go test -race -count=1 . para verificar a correção neste cenário. A documentação de sync.Mutex e sync.WaitGroup descreve as garantias de sincronização usadas no exemplo; para outras causas, veja como corrigir data races em Go.
Requisitos e uso no CI
O detector requer cgo, um compilador C instalado e uma plataforma suportada. Se aparecer -race requires cgo, confira go env CGO_ENABLED GOOS GOARCH e os requisitos oficiais. Definir CGO_ENABLED=1 não instala o compilador nem torna uma plataforma incompatível suportada.
No projeto real, use go test -race ./... regularmente no CI. Para investigar um teste específico sem reaproveitar resultados, rode go test -race -count=1 -run '^TestNome$' ./pacote. Repetir cenários pode expor conflitos adicionais, mas não substitui casos que exercitem os caminhos concorrentes relevantes.
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.
go test, go test -race e go vet: qual usar?
Os três comandos se complementam; um não substitui o outro:
| Comando | Use para | Limite principal |
|---|---|---|
go test ./... | Compilar os pacotes e executar testes, exemplos e checks associados | Só valida os comportamentos cobertos pela suíte |
go test -race ./... | Executar a suíte com o detector de data races | Só encontra corridas nos caminhos executados e custa mais tempo e memória |
go vet ./... | Procurar construções suspeitas por análise estática | Não executa a suíte nem prova que o programa está correto |
A documentação oficial detalha o fluxo de go test, o funcionamento e as plataformas do race detector e os checks do go vet. Em um pipeline mínimo, rode primeiro go vet ./... e go test ./...; mantenha go test -race ./... em todas as mudanças ou em um job separado quando a instrumentação deixar o feedback lento.
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. Golang test é só outra forma de buscar o mesmo comando oficial go test.
go run tests funciona?
Não. go run executa um pacote main e ignora a suíte. Para unit tests, integração, -race, cobertura e benchmarks, use go test — por exemplo go test ./... ou go test -run '^TestNome$' ./pacote.
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: Outubro de 2026 — exemplo reproduzível de data race, leitura do relatório e correção com mutex; resposta direta para go test, golang test e go testing, diferença de go run, uso conjunto de go test -race e go vet, table-driven tests, cleanup, mocks e fakes, httptest, integração, cobertura, benchmarks com b.Loop, fuzzing e CI.