Capítulo 15: Testes Unitários e de Integração com httptest e Testify
Especialização em Backend com Go & Gin • Gin Framework & Go 1.22+ • GORM, Clean Architecture, Concorrência e Alta Performance
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
B --> C["Controller / Handler"]
C --> D["Service Layer (Regras de Negócio)"]
D --> E["Repository / ORM (Persistência)"]
E --> F["Banco de Dados / Cache"]
subgraph ARQ["Arquitetura do Capítulo"]
G["Conceito: Testes Unitários e de Integração com httptest e Testify"]
H["Segurança, Validação e Resiliência"]
I["Alta Performance e Escalabilidade"]
end
D --> ARQ
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. Fundamentos Técnicos de Testes Unitários e de Integração com httptest e Testify
O pacote padrão net/http/httptest permite testar handlers Gin sem abrir uma porta TCP real: httptest.NewRecorder() cria um http.ResponseWriter que grava a resposta em memória (código de status, headers, corpo) para inspeção posterior, e httptest.NewRequest monta uma *http.Request sintética. Como o *gin.Engine implementa http.Handler, basta chamar router.ServeHTTP(w, req) diretamente — o Gin executa toda a cadeia de middlewares e o handler normalmente, mas tudo acontece dentro do processo de teste, em microsegundos, sem rede, sockets ou timing de I/O real.
gin.SetMode(gin.TestMode) // silencia logs de debug do Gin durante os testes
r := gin.New()
r.GET("/ping", handler)
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/ping", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"msg":"pong"}`, w.Body.String())
A biblioteca github.com/stretchr/testify complementa o pacote testing nativo (não o substitui) com três módulos usados em conjunto: assert gera mensagens de falha legíveis sem abortar o teste (assert.Equal, assert.JSONEq — que compara JSON semanticamente, ignorando ordem de chaves e espaçamento); require faz a mesma checagem mas aborta o teste imediatamente em caso de falha (usado quando um teste subsequente dependeria de um valor que falhou, como um ponteiro nulo); e mock fornece a base para test doubles de interfaces, essencial para isolar a camada de Service da camada de Repository/banco de dados em testes unitários — a interface Repository é satisfeita por um struct que embute mock.Mock, cujo método On("Metodo", args...).Return(valores...) configura o comportamento esperado, e AssertExpectations(t) verifica que todas as chamadas configuradas de fato ocorreram.
A distinção entre teste unitário e teste de integração é sobre o que está sendo verificado, não a ferramenta: um teste unitário do Service usa um mock do Repository para validar regra de negócio pura, isolada de I/O; um teste de integração troca o mock por um banco real (ou um SQLite file::memory:?cache=shared, que roda inteiramente em RAM e é descartado ao fim do processo) para validar que a camada de persistência (queries, migrações, constraints) também funciona — mais lento, mas cobre a integração real entre as camadas.
Para medir performance de forma confiável, o Go oferece benchmarks nativos via testing.B: uma função func BenchmarkX(b *testing.B) roda o corpo b.N vezes, com b.N ajustado automaticamente até a medição estabilizar estatisticamente; b.ResetTimer() zera o cronômetro após qualquer setup custoso que não deva contar na medição. go test -bench=. -benchmem reporta ns/op (tempo por operação) e allocs/op (alocações de heap por operação) — útil para detectar regressões de performance introduzidas por uma refatoração aparentemente inofensiva.
Por fim, para código concorrente (comum em handlers que disparam goroutines), go test -race ./... habilita o detector de data race do Go (baseado em ThreadSanitizer): ele instrumenta acessos à memória em tempo de execução e reporta com stack trace exato quando duas goroutines acessam a mesma variável sem sincronização e pelo menos uma delas escreve — um bug que passaria despercebido em execução normal (e até em produção, por longos períodos) é pego de forma determinística durante os testes.
💻 2. Código de Demonstração Corporativo
package handlers_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockProdutoRepo struct{ mock.Mock }
func (m *MockProdutoRepo) BuscarPorID(id uint) (*Produto, error) {
args := m.Called(id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*Produto), args.Error(1)
}
// TestGetProduto_HTTP200 valida handler + serviço isolados do banco real:
// o mock do repositório substitui a camada de persistência.
func TestGetProduto_HTTP200(t *testing.T) {
gin.SetMode(gin.TestMode)
mockRepo := new(MockProdutoRepo)
mockRepo.On("BuscarPorID", uint(1)).Return(&Produto{ID: 1, Nome: "Teclado"}, nil)
r := gin.New()
r.GET("/produtos/:id", NovoHandler(mockRepo).BuscarPorID)
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/produtos/1", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"id":1,"nome":"Teclado"}`, w.Body.String())
mockRepo.AssertExpectations(t)
}
🔗 Recursos Pedagógicos do Capítulo 15
| Recurso Didático | Finalidade | Link de Acesso |
|---|---|---|
| 📊 Slides de Aula | Apresentação visual interativa com Dark Mode e suporte a teclado | Ver Slides |
| 🧠 Quiz Formativo | Teste interativo de fixação com feedback imediato por alternativa | Fazer Quiz |
| 💻 Exemplos de Código | Demonstrações funcionais com código executável | Ver Exemplos |
| 🧩 Exercícios em 4 Níveis | Lista progressiva de fixação com gabarito em bloco colapsável | Resolver Exercícios |
| ⬅️ Capítulo Anterior | 📚 Sumário de Tópicos | Próximo Capítulo ➡️ |