A maioria das equipes tem uma versão desta conversa: "Nossa cobertura de testes está em 87%, mas todo deploy é estressante." O número é alto, mas a confiança é baixa. O que está errado?
Cobertura de código mede quantas linhas foram executadas durante os testes. Não mede se os testes verificam comportamento correto, se cobrem os casos que realmente importam, ou se um refactor que quebra a aplicação vai ser detectado. Você pode ter 100% de cobertura e uma suite de testes que não testa nada de útil.
A pirâmide de testes revisitada
O modelo clássico de pirâmide (muitos unit tests, poucos integration tests, menos E2E) ainda é válido como princípio — mas é frequentemente mal aplicado.
O erro mais comum: escrever centenas de unit tests que testam implementação, não comportamento. Quando o código é refatorado, todos os testes quebram — mesmo que o comportamento externo não mudou. Isso gera um feedback negativo forte: refatoração = dor. Times que passam por isso param de refatorar.
O princípio correto, popularizado por Kent C. Dodds: quanto mais seus testes se parecem com a forma que o software é usado, mais confiança eles dão.
Testando comportamento, não implementação
A distinção prática:
// ❌ Testa implementação — frágil
it('should call setLoading(true) then fetch then setLoading(false)', () => {
const setLoading = jest.fn()
// ... testa detalhes internos
})
// ✅ Testa comportamento — confiável
it('shows spinner while loading, then displays results', async () => {
render(<ProductList />)
expect(screen.getByRole('progressbar')).toBeInTheDocument()
await screen.findByText('Product A')
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
expect(screen.getByText('Product A')).toBeInTheDocument()
})
O primeiro teste quebra em qualquer refactor da implementação. O segundo sobrevive a qualquer refactor que preserve o comportamento visível.
Os casos que realmente importam cobrir
Em vez de perseguir porcentagem, pense em categorias de cobertura que importam:
Caminho feliz
O fluxo principal funciona? Input válido produz output esperado? É o mínimo — necessário mas não suficiente.
Casos de borda
O que acontece com input vazio? Com valores extremos? Com null onde não se espera? Com strings muito longas? A maioria dos bugs reais vive aqui.
Casos de erro
O que acontece quando a API externa falha? Quando o banco está indisponível? Quando o usuário não tem permissão? Se você não testa os caminhos de erro, você não sabe o que seus usuários vão ver quando algo der errado.
Invariantes de negócio
As regras que nunca podem ser violadas: saldo não pode ficar negativo, usuário não pode acessar dados de outro usuário, pedido não pode ser aprovado sem item. Esses testes são a documentação executável das regras de negócio.
Integration tests: o sweet spot ignorado
Para a maioria das aplicações, integration tests são onde o ROI de testes é maior. Um integration test que exercita o endpoint real (com banco de dados em memória ou de teste) cobre mais comportamento com menos código do que dezenas de unit tests isolados.
// Integration test com banco de teste — alta confiança
describe('POST /orders', () => {
it('creates order and deducts inventory', async () => {
await db.product.create({ id: 'p1', stock: 5 })
const res = await request(app)
.post('/orders')
.set('Authorization', `Bearer ${userToken}`)
.send({ productId: 'p1', quantity: 2 })
expect(res.status).toBe(201)
expect(res.body.order.status).toBe('confirmed')
const product = await db.product.findUnique({ where: { id: 'p1' } })
expect(product.stock).toBe(3)
})
})
Esse teste único valida: autenticação funciona, validação de input funciona, a lógica de pedido funciona, a atualização de estoque funciona, a resposta está correta. Cinco comportamentos em um teste.
A métrica certa para medir confiança
Em vez de cobertura de linhas, meça:
- Quantos bugs foram para produção que a suite não detectou nos últimos 3 meses? Se a resposta for alta, você tem gap de cobertura em comportamentos importantes.
- Quanto tempo você gasta "consertando" testes que quebraram sem que o comportamento mudasse? Se for alto, você tem testes frágeis que custam mais do que entregam.
- Você faz deploy confiante ou rezando? A resposta honesta diz mais sobre a qualidade dos testes do que qualquer número de cobertura.
Cobertura de 60% com testes bem escritos dá mais confiança do que cobertura de 90% com testes que testam implementação. Otimize para confiança, não para métrica.