A pirâmide de testes na prática: unitário, integração e E2E sem dogma
Todo time concorda que "precisa testar mais". O desacordo começa em como. Um lado enche a suíte de testes end-to-end que reproduzem o clique do usuário e, meses depois, tem uma bateria lenta, instável e que ninguém confia. O outro escreve mil testes unitários que passam com 100% de cobertura enquanto o produto quebra em produção, porque nenhum deles exercita as peças conversando de verdade.
A pirâmide de testes é a heurística que equilibra essa tensão. Não é uma lei nem um número mágico de cobertura. É uma forma de pensar sobre onde investir esforço de teste para ter o máximo de confiança pelo menor custo de manutenção. Este guia percorre as camadas e, principalmente, os antipadrões que fazem uma suíte virar dívida.
O que a pirâmide realmente diz
A pirâmide tem três camadas, e a proporção entre elas é o recado central:
- Base larga, os testes unitários: muitos, rápidos, baratos. Testam uma unidade isolada.
- Meio, os testes de integração: menos, mais lentos. Testam peças conversando.
- Topo estreito, os testes end-to-end (E2E): poucos, lentos, caros. Testam o sistema inteiro pela ótica do usuário.
O formato não é estético: reflete custo e velocidade. Quanto mais alto na pirâmide, mais lento o teste roda, mais frágil ele é e mais caro é mantê-lo. Por isso você quer muitos testes baratos embaixo e poucos testes caros no topo, não o contrário.
| Camada | Escopo | Velocidade | Custo de manutenção | Quantidade ideal |
|---|---|---|---|---|
| Unitário | Uma função/classe isolada | Milissegundos | Baixo | Muitos |
| Integração | Módulos + banco/API reais | Segundos | Médio | Alguns |
| E2E | Fluxo completo pela UI | Segundos a minutos | Alto | Poucos |
Camada 1: testes unitários
Um teste unitário verifica uma unidade de lógica em isolamento: uma função de cálculo de imposto, uma regra de validação, uma máquina de estados. Ele não toca banco, rede nem disco. Roda em milissegundos, então você pode ter milhares e rodá-los a cada save.
O maior valor do teste unitário está na lógica de negócio densa: cálculos, regras condicionais, transformações, casos de borda. É onde bugs se escondem e onde um teste rápido dá retorno imediato.
O erro clássico é testar a implementação em vez do comportamento. Um teste que verifica "o método X chamou o método Y" quebra a cada refatoração interna, mesmo que o comportamento visível não mude. Teste o que a unidade faz, não como ela faz. Assim você refatora à vontade e os testes só reclamam quando o comportamento realmente muda.
Mocks, stubs e fakes
Para isolar a unidade, você substitui suas dependências:
- Stub: devolve respostas fixas (
retorna sempre 42). - Mock: um stub que também verifica interações (
foi chamado uma vez com X). - Fake: uma implementação leve e funcional (um repositório em memória no lugar do banco).
Regra prática: prefira fakes a mocks quando puder. Um repositório em memória testa comportamento real e sobrevive a refatorações; um mock que grava expectativas de chamadas acopla o teste à implementação e vira frágil. Mock demais é um cheiro: sinaliza que a unidade tem dependências demais ou que você está testando fiação em vez de lógica.
Camada 2: testes de integração
Testes unitários provam que cada peça funciona sozinha; eles não provam que as peças funcionam juntas. É aí que entra a integração: testar seu código contra o banco real, a API real, a fila real, as fronteiras onde os bugs de verdade costumam morar.
Boa parte dos defeitos de produção não está na lógica pura, e sim nas costuras: uma query que não retorna o esperado, uma serialização quebrada, uma migração que faltou, um contrato de API que mudou. Nenhum teste unitário com mock pega isso, porque o mock mente sobre como a dependência se comporta.
Testes de integração são mais lentos (sobem um banco, fazem I/O), então você tem menos deles, focados nos fluxos críticos e nas fronteiras de maior risco. Ferramentas como containers efêmeros (subir um PostgreSQL descartável por execução) tornam essa camada confiável e reproduzível, sem depender de um banco compartilhado que vive sujo.
Camada 3: testes end-to-end
O teste E2E exercita o sistema inteiro como o usuário faz: abre o navegador, preenche o formulário, clica em salvar e confere o resultado na tela. É o teste de maior fidelidade: quando passa, você tem alta confiança de que o fluxo funciona de ponta a ponta.
Também é o mais caro e mais frágil. É lento (sobe navegador, espera renderização), depende de ambiente completo e é sensível a qualquer mudança de UI. Por isso o topo da pirâmide é estreito: cubra com E2E apenas os caminhos críticos de negócio: o happy path do login, do checkout, do cadastro. Não tente cobrir cada variação de validação com E2E; isso é trabalho da base da pirâmide.
Os antipadrões que quebram a estratégia
O cone de sorvete
O antipadrão mais comum é a pirâmide invertida, ou cone de sorvete: muitos testes E2E, poucos de integração e quase nenhum unitário. Acontece naturalmente quando o time testa "pela tela" porque parece mais realista. O resultado é uma suíte que demora 40 minutos, quebra por qualquer motivo e, quando falha, não diz onde está o problema, só que "algo no fluxo quebrou". A confiança despenca e o time começa a ignorar as falhas. Inverta o cone: empurre a lógica para testes unitários e reserve o E2E para o essencial.
Testes flaky
Um teste flaky passa e falha de forma intermitente, sem que o código tenha mudado, geralmente por espera fixa (sleep), dependência de ordem de execução, estado compartilhado ou concorrência. Ele é pior que não ter teste: ensina o time a ignorar o vermelho. Quando um flaky é tolerado, a falha real do dia seguinte também é ignorada. Trate flaky como bug de prioridade alta: corrija a causa (espere por condição, não por tempo; isole o estado) ou quarentene o teste até corrigir. Nunca conviva com ele.
Cobertura como meta
Cobertura de código mede o que foi executado, não o que foi verificado. Dá para ter 100% de cobertura sem uma única asserção significativa. Perseguir um número (exige 90%!) leva a testes inúteis escritos só para colorir o relatório. Use cobertura como bússola (ela aponta zonas totalmente sem teste, que merecem atenção), nunca como meta contratual. A pergunta certa não é "qual a cobertura?", e sim "se essa regra quebrar, algum teste falha?".
Como decidir o que testar em cada camada
Sem dogma, com um princípio: empurre cada teste para a camada mais baixa que ainda dá a confiança necessária.
- Lógica de negócio, cálculos e casos de borda → unitário. É barato e específico.
- Fronteiras com banco, filas e APIs → integração. É onde os bugs de costura vivem.
- Fluxos de negócio críticos de ponta a ponta → E2E, poucos e bem escolhidos.
A pirâmide não é uma prescrição rígida: alguns times de backend rico em I/O acham mais valor em uma camada de integração robusta; times de front-end pesado investem mais no topo. O que não muda é a lógica econômica por trás dela: teste rápido e específico embaixo, teste lento e abrangente no topo, e confiança máxima pelo menor custo de manutenção. Uma suíte de testes existe para você entregar rápido com segurança. No minuto em que ela vira um obstáculo lento e não confiável, a estratégia está errada, não os testes em si.
Sua suíte de testes virou um gargalo em vez de uma rede de segurança? Na ConstructorTI, montamos estratégias de teste que aceleram a entrega em vez de travá-la: pirâmide equilibrada, integração com bancos efêmeros, E2E enxuto nos fluxos críticos e caça a flaky. Se o seu deploy trava esperando testes lentos ou você não confia no verde do CI, fale com a nossa engenharia e receba um diagnóstico da sua estratégia de qualidade.