Introdução: Por que testar em Go é importante?
Go (Golang) é uma linguagem que, desde o seu design, é focada em simplicidade, desempenho e confiabilidade. O pacote embutido testing e ferramentas poderosas, como go test, tornam o processo de escrever testes uma parte essencial do desenvolvimento. Sem testes de qualidade, mesmo o código mais elegante se transforma com o tempo em um sistema "frágil", onde uma única alteração quebra tudo.
Neste artigo, vamos analisar três níveis-chave de teste: unit (unitário), integration (integração) e e2e (end-to-end). Você aprenderá como eles diferem, quando aplicá-los e como escrever testes eficazes em Go. O material será útil tanto para iniciantes quanto para desenvolvedores experientes que desejam sistematizar seus conhecimentos.
1. Teste Unitário
Testes unitários são a base da pirâmide de testes. Eles verificam o funcionamento de funções, métodos ou módulos individuais de forma isolada de dependências externas (banco de dados, rede, sistema de arquivos).
Princípios básicos dos testes unitários em Go
- Isolamento: O código testado não deve depender de serviços externos.
- Rapidez: Testes unitários são executados em milissegundos.
- Determinismo: O resultado do teste não deve mudar de uma execução para outra.
Exemplo de um teste unitário simples
Suponha que temos uma função para somar dois números:
// math.gopackage math
func Add(a, b int) int { return a + b}O teste para esta função será assim:
// math_test.gopackage math
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) }}O teste é executado com o comando: go test ./...
Testes orientados a tabela (Table-driven tests)
A comunidade Go prefere testes orientados a tabela para verificar múltiplos casos:
func TestAddTable(t *testing.T) { tests := []struct { a, b, expected int }{ {1, 1, 2}, {0, 0, 0}, {-1, 1, 0}, {100, 200, 300}, }
for _, tt := range tests { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }}Dica: Use t.Run() para criar testes aninhados com nomes descritivos — isso melhora a legibilidade dos relatórios.
2. Teste de Integração
Testes de integração verificam a interação entre componentes do sistema: banco de dados, API, serviços externos. Ao contrário dos testes unitários, aqui levantamos dependências reais (ou quase reais).
Características dos testes de integração em Go
- Frequentemente exigem configuração de ambiente (por exemplo, um banco de dados de teste).
- Podem usar build tags para separar testes unitários e de integração.
- São executados mais lentamente, por isso são executados separadamente.
Exemplo: testando interação com banco de dados (PostgreSQL)
Criaremos um arquivo com a build tag integration:
// db_test.go//go:build integration
package db
import ( "database/sql" "testing")
func TestSaveUser(t *testing.T) { // Conectando ao banco de dados de teste dsn := "host=localhost user=test password=test dbname=testdb sslmode=disable" db, err := sql.Open("postgres", dsn) if err != nil { t.Fatalf("failed to connect to db: %v", err) } defer db.Close()
// Limpando os dados antes do teste _, _ = db.Exec("TRUNCATE TABLE users")
// Executando a operação sendo testada user := User{Name: "Alice", Email: "alice@example.com"}