Testes em Go: Unit, Integration, E2E — guia completo
Testes são uma parte essencial do desenvolvimento de software de qualidade. Na linguagem Go (Golang), os testes são incorporados ao nível da biblioteca padrão, tornando-os simples e eficientes. Neste artigo, vamos abordar os três principais níveis de teste: testes unitários (unit), de integração (integration) e ponta a ponta (e2e). Você aprenderá como escrevê-los, quando aplicá-los e quais ferramentas utilizar.
Por que os testes em Go são tão importantes?
Go foi criado com foco em simplicidade e confiabilidade. O pacote embutido testing e a ferramenta go test permitem adicionar testes facilmente a qualquer projeto. A equipe Go segue a filosofia: "Se o código não foi testado, ele não funciona". Os testes ajudam a:
- Detectar erros nas fases iniciais do desenvolvimento
- Simplificar a refatoração e a manutenção do código
- Documentar o comportamento de funções e módulos
- Aumentar a confiança na estabilidade da aplicação
Testes unitários em Go
Testes unitários verificam o funcionamento de funções, métodos ou estruturas individuais de forma isolada de dependências externas. Em Go, eles são escritos em arquivos com o sufixo _test.go.
Fundamentos dos testes unitários
Vamos considerar um exemplo simples. Suponha que temos uma função de soma:
// math.gopackage main
func Add(a, b int) int { return a + b}O teste para esta função será assim:
// 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) }}Execução do teste: go test -v
Testes orientados a tabela (Table-driven tests)
Go incentiva o uso de testes orientados a tabela para verificar múltiplos cenários:
func TestAddTableDriven(t *testing.T) { tests := []struct { name string a, b int want int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -1, -2, -3}, {"zero", 0, 0, 0}, {"mixed", -5, 10, 5}, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Add(tt.a, tt.b) if got != tt.want { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) } }) }}Testes com mocks (Mock)
Para isolar testes unitários, frequentemente são necessários mocks. Em Go, você pode usar interfaces e bibliotecas como gomock ou testify/mock.
// Definindo a interfacetype UserRepository interface { GetUser(id int) (User, error)}
// Teste usando testifyfunc TestGetUserName(t *testing.T) { mockRepo := new(MockUserRepository) mockRepo.On("GetUser", 1).Return(User{Name: "Alice"}, nil)
service := NewUserService(mockRepo) name, err := service.GetUserName(1)
assert.NoError(t, err) assert.Equal(t, "Alice", name) mockRepo.AssertExpectations(t)}Testes de integração em Go
Testes de integração verificam a interação entre componentes do sistema: bancos de dados, APIs, sistemas de arquivos e outros serviços externos. Em Go, é comum colocá-los em pacotes separados ou usar build tags.
Testes com banco de dados
Para testar a interação com o banco de dados, frequentemente são usados testcontainers ou bancos de dados temporários:
// integration_test.go (com build tag)//go:build integration
package integration
import ( "database/sql" "testing" _ "github.com/lib/pq")
func TestUserRepository(t *testing.T) { // Conexão com o BD de teste db, err := sql.Open("postgres", "host=localhost port=
```