Testes Unitários em Go: Estrutura & Boas Práticas
Testes em Go, do básico aos padrões avançados
O pacote de teste embutido no Go fornece um poderoso e minimalista framework para escrever testes unitários sem dependências externas. Aqui estão os fundamentos dos testes, a estrutura do projeto e padrões avançados para construir aplicações Go confiáveis.

Por que os testes importam no Go
A filosofia do Go enfatiza simplicidade e confiabilidade. A biblioteca padrão inclui o pacote testing, tornando os testes unitários um cidadão de primeira classe no ecossistema Go. Código Go bem testado melhora a manutenibilidade, detecta bugs cedo e fornece documentação por meio de exemplos. Se você é novo no Go, consulte nossa Folha de Dicas do Go para uma referência rápida dos fundamentos da linguagem.
Principais benefícios dos testes no Go:
- Suporte embutido: Não são necessários frameworks externos
- Execução rápida: Execução de testes concorrente por padrão
- Sintaxe simples: Pouca quantidade de código de boilerplate
- Ferramentas ricas: Relatórios de cobertura, benchmarks e perfilamento
- Amigável para CI/CD: Integração fácil com pipelines automatizados
Estrutura do Projeto para Testes Go
Os testes Go vivem ao lado do seu código de produção com uma convenção clara de nomes:
myproject/
├── go.mod
├── main.go
├── calculator.go
├── calculator_test.go
├── utils/
│ ├── helper.go
│ └── helper_test.go
└── models/
├── user.go
└── user_test.go
Convenções importantes:
- Os arquivos de teste terminam com
_test.go - Os testes estão no mesmo pacote que o código (ou usam o sufixo
_testpara testes de caixa preta) - Cada arquivo de origem pode ter um arquivo de teste correspondente
Abordagens de Teste por Pacote
Teste de caixa branca (mesmo pacote):
package calculator
import "testing"
// Pode acessar funções e variáveis não exportadas
Teste de caixa preta (pacote externo):
package calculator_test
import (
"testing"
"myproject/calculator"
)
// Pode acessar apenas funções exportadas (recomendado para APIs públicas)
Estrutura Básica dos Testes
Cada função de teste segue este padrão:
package calculator
import "testing"
// A função de teste deve começar com "Test"
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
Métodos de testing.T:
t.Error()/t.Errorf(): Marca o teste como falhado, mas continuat.Fatal()/t.Fatalf(): Marca o teste como falhado e para imediatamentet.Log()/t.Logf(): Registra saída (apenas mostrado com a bandeira-v)t.Skip()/t.Skipf(): Pula o testet.Parallel(): Executa o teste em paralelo com outros testes em paralelo
Testes com Tabela: A Maneira Go
Testes com tabela são a abordagem idiomática do Go para testar múltiplos cenários. Com Generics no Go, você também pode criar ajudantes de teste seguros de tipo que funcionam com diferentes tipos de dados:
func TestCalculate(t *testing.T) {
tests := []struct {
name string
a, b int
op string
expected int
wantErr bool
}{
{"adição", 2, 3, "+", 5, false},
{"subtração", 5, 3, "-", 2, false},
{"multiplicação", 4, 3, "*", 12, false},
{"divisão", 10, 2, "/", 5, false},
{"divisão por zero", 10, 0, "/", 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Calculate(tt.a, tt.b, tt.op)
if (err != nil) != tt.wantErr {
t.Errorf("Calculate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if result != tt.expected {
t.Errorf("Calculate(%d, %d, %q) = %d; want %d",
tt.a, tt.b, tt.op, result, tt.expected)
}
})
}
}
Vantagens:
- Uma única função de teste para múltiplos cenários
- Fácil adição de novos casos de teste
- Documentação clara do comportamento esperado
- Melhor organização e manutenibilidade dos testes
Executando Testes
Comandos Básicos
# Executar testes no diretório atual
go test
# Executar testes com saída detalhada
go test -v
# Executar testes em todos os subdiretórios
go test ./...
# Executar um teste específico
go test -run TestAdd
# Executar testes que correspondem a um padrão
go test -run TestCalculate/addition
# Executar testes em paralelo (padrão é GOMAXPROCS)
go test -parallel 4
# Executar testes com timeout
go test -timeout 30s
Cobertura de Testes
# Executar testes com cobertura
go test -cover
# Gerar perfil de cobertura
go test -coverprofile=coverage.out
# Visualizar cobertura no navegador
go tool cover -html=coverage.out
# Mostrar cobertura por função
go tool cover -func=coverage.out
# Definir modo de cobertura (set, count, atomic)
go test -covermode=count -coverprofile=coverage.out
Flags Úteis
-short: Executar testes marcados comif testing.Short()-race: Habilitar detector de corrida (encontra problemas de acesso concorrente)-cpu: Especificar valores de GOMAXPROCS-count n: Executar cada teste n vezes-failfast: Parar na primeira falha de teste
Funções de Ajuda e Configuração/Desmontagem
Funções de Ajuda
Marque funções de ajuda com t.Helper() para melhorar o relatório de erros:
func assertEqual(t *testing.T, got, want int) {
t.Helper() // Esta linha é reportada como o chamador
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
func TestMath(t *testing.T) {
result := Add(2, 3)
assertEqual(t, result, 5) // Linha de erro aponta aqui
}
Configuração e Desmontagem
func TestMain(m *testing.M) {
// Código de configuração aqui
setup()
// Executar testes
code := m.Run()
// Código de desmontagem aqui
teardown()
os.Exit(code)
}
Fixtures de Teste
func setupTestCase(t *testing.T) func(t *testing.T) {
t.Log("setup test case")
return func(t *testing.T) {
t.Log("teardown test case")
}
}
func TestSomething(t *testing.T) {
teardown := setupTestCase(t)
defer teardown(t)
// Código de teste aqui
}
Mocking e Injeção de Dependência
Mocking Baseado em Interface
Quando testando código que interage com bancos de dados, usar interfaces facilita a criação de implementações de mock. Se você estiver trabalhando com PostgreSQL no Go, veja nossa comparação de ORMs do Go para escolher a biblioteca de banco de dados certa com boa testabilidade.
// Código de produção
type Database interface {
GetUser(id int) (*User, error)
}
type UserService struct {
db Database
}
func (s *UserService) GetUserName(id int) (string, error) {
user, err := s.db.GetUser(id)
if err != nil {
return "", err
}
return user.Name, nil
}
// Código de teste
type MockDatabase struct {
users map[int]*User
}
func (m *MockDatabase) GetUser(id int) (*User, error) {
if user, ok := m.users[id]; ok {
return user, nil
}
return nil, errors.New("user not found")
}
func TestGetUserName(t *testing.T) {
mockDB := &MockDatabase{
users: map[int]*User{
1: {ID: 1, Name: "Alice"},
},
}
service := &UserService{db: mockDB}
name, err := service.GetUserName(1)
if err != nil {
t.Fatalf("erro inesperado: %v", err)
}
if name != "Alice" {
t.Errorf("got %s, want Alice", name)
}
}
Bibliotecas de Teste Populares
Testify
A biblioteca de teste mais popular do Go para afirmações e mocks:
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestWithTestify(t *testing.T) {
result := Add(2, 3)
assert.Equal(t, 5, result, "eles devem ser iguais")
assert.NotNil(t, result)
}
// Exemplo de mock
type MockDB struct {
mock.Mock
}
func (m *MockDB) GetUser(id int) (*User, error) {
args := m.Called(id)
return args.Get(0).(*User), args.Error(1)
}
Outras Ferramentas
- gomock: Framework de mock do Google com geração de código
- httptest: Biblioteca padrão para testar manipuladores HTTP
- testcontainers-go: Testes de integração com contêineres Docker
- ginkgo/gomega: Framework de teste estilo BDD
Quando testando integrações com serviços externos, como modelos de IA, você precisará mockar ou stubar essas dependências. Por exemplo, se você estiver usando Ollama no Go, considere criar wrappers de interface para tornar seu código mais testável.
Testes de Benchmark
O Go inclui suporte embutido para benchmarks:
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
// Executar benchmarks
// go test -bench=. -benchmem
A saída mostra iterações por segundo e alocações de memória.
Boas Práticas
- Escreva testes com tabela: Use o padrão de slice de structs para múltiplos casos de teste
- Use
t.Runpara subtestes: Melhor organização e pode executar subtestes seletivamente - Teste primeiramente funções exportadas: Foque no comportamento da API pública
- Mantenha os testes simples: Cada teste deve verificar uma coisa
- Use nomes significativos para testes: Descreva o que está sendo testado e o resultado esperado
- Não teste detalhes de implementação: Teste comportamento, não internos
- Use interfaces para dependências: Facilita o mock
- Aim for high coverage, but quality over quantity: 100% de cobertura não significa livre de bugs
- Run tests with -race flag: Detecte problemas de concorrência cedo
- Use TestMain para configurações caras: Evite repetir a configuração em cada teste
Exemplo: Suite de Testes Completa
package user
import (
"errors"
"testing"
)
type User struct {
ID int
Name string
Email string
}
func ValidateUser(u *User) error {
if u.Name == "" {
return errors.New("o nome não pode estar vazio")
}
if u.Email == "" {
return errors.New("o e-mail não pode estar vazio")
}
return nil
}
// Arquivo de teste: user_test.go
func TestValidateUser(t *testing.T) {
tests := []struct {
name string
user *User
wantErr bool
errMsg string
}{
{
name: "usuário válido",
user: &User{ID: 1, Name: "Alice", Email: "alice@example.com"},
wantErr: false,
},
{
name: "nome vazio",
user: &User{ID: 1, Name: "", Email: "alice@example.com"},
wantErr: true,
errMsg: "o nome não pode estar vazio",
},
{
name: "e-mail vazio",
user: &User{ID: 1, Name: "Alice", Email: ""},
wantErr: true,
errMsg: "o e-mail não pode estar vazio",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateUser(tt.user)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && err.Error() != tt.errMsg {
t.Errorf("ValidateUser() error message = %v, want %v", err.Error(), tt.errMsg)
}
})
}
}
Links Úteis
- Documentação Oficial do Pacote de Teste do Go
- Blog do Go: Testes com Tabela
- Repositório GitHub do Testify
- Documentação do GoMock
- Aprenda Go com Testes
- Ferramenta de Cobertura de Código do Go
- Folha de Dicas do Go
- Comparação de ORMs do Go para PostgreSQL: GORM vs Ent vs Bun vs sqlc
- SDKs do Go para Ollama - comparação com exemplos
- Aplicações CLI no Go com Cobra & Viper
- Generics no Go: Casos de Uso e Padrões
Conclusão
O framework de teste do Go fornece tudo o que é necessário para testes unitários abrangentes com mínima configuração. Ao seguir idiomas Go como testes com tabela, usar interfaces para mock, e aproveitar ferramentas embutidas, você pode criar suites de testes mantíveis e confiáveis que crescem com sua base de código.
Essas práticas de teste se aplicam a todos os tipos de aplicações Go, desde serviços web até aplicações CLI construídas com Cobra & Viper. Testar ferramentas de linha de comando requer padrões semelhantes com foco adicional no teste de entrada/saída e análise de bandeiras.
Comece com testes simples, adicione gradualmente cobertura e lembre-se de que o teste é um investimento na qualidade do código e na confiança do desenvolvedor. O foco da comunidade Go em testes torna mais fácil manter projetos a longo prazo e colaborar efetivamente com membros da equipe.