A pirâmide de testes e suas variações
Resumo em uma linha
A forma da sua suíte (quantos testes de cada tipo) não é um dogma decorado — é a sombra de onde o risco mora no seu sistema, e a pergunta certa é “qual teste eu quero que falhe quando ESTE bug aparecer?“.
Você já sabe o que é um teste e por que escrever um — isso ficou em 01 - O que são testes e por que testar. Agora vem a pergunta que separa quem decora de quem entende: quantos testes de cada tipo você deveria ter?
A resposta preguiçosa é “siga a pirâmide”. A resposta de quem já apanhou de uma suíte lenta e frágil é: “depende de onde mora o risco”. Esta nota destrincha as duas.
Os três níveis: a anatomia de uma suíte
Antes de falar de forma, precisamos dos tijolos. Quase toda suíte automatizada se organiza em três alturas. Não são categorias herméticas — há um espectro contínuo —, mas pensar em três degraus ajuda a raciocinar.
Unitário. Testa uma peça pequena e isolada — uma função, um método, uma classe — sem rede, sem banco, sem arquivo. Roda em milissegundos. Você tem muitos. Quando falha, o dedo já aponta pra linha culpada. Detalhes em 04 - Testes unitários.
Integração. Testa colaboração: dois ou mais componentes conversando de verdade. O controller chama o service, que chama o repositório, que bate num banco real (ou num container efêmero). Não verifica “o método X retorna Y”; verifica “a fiação entre X, Y e Z está certa”. Mais lento que unit, mais valioso por teste. Detalhes em 07 - Testes de integração.
E2E (ponta a ponta). Testa o sistema inteiro pela porta da frente — o navegador, a API pública, o app. Simula o usuário de verdade: clica, digita, espera. É o mais próximo da realidade e o mais caro de tudo: lento, frágil, difícil de debugar quando quebra (“falhou em algum lugar entre o browser e o banco — boa sorte”).
Por que a forma importa tanto?
Porque cada nível negocia quatro variáveis ao mesmo tempo: custo de escrita e manutenção, velocidade de execução, confiança (quão perto da realidade) e localização da falha (quão rápido você acha o culpado). Unit é barato, rápido e preciso, mas confia pouco no “tudo junto”. E2E confia muito, mas é caro, lento e impreciso. A forma da suíte é como você distribui essa aposta.
A tabela abaixo é o mapa mental que você quer ter na cabeça numa entrevista.
| Nível | Velocidade | Confiança (perto da realidade) | Custo de manutenção | Quando usar |
|---|---|---|---|---|
| Unitário | Altíssima (ms) | Baixa por teste | Baixo | Lógica isolada, regras de negócio, branches, edge cases |
| Integração | Média (s) | Alta | Médio | Fiação entre camadas, contratos com banco/fila/HTTP |
| E2E | Baixa (min) | Altíssima | Alto | Fluxos críticos de usuário ponta a ponta |
Repare na tensão: confiança e velocidade andam em sentidos opostos. Subir o nível compra realismo e paga em tempo e fragilidade. Toda discussão de “forma da suíte” é uma negociação dessa troca.
A pirâmide de testes (Mike Cohn)
A imagem mais famosa do testing. Mike Cohn a popularizou em Succeeding with Agile (2009); Martin Fowler a refinou e desenhou no clássico texto The Practical Test Pyramid. A intuição é geométrica e simples.
Pense na pirâmide do Egito: base larga e estável, topo estreito. A base larga são os testes unitários — muitos, baratos, rápidos. Conforme você sobe, os testes ficam mais caros e mais lentos, então você tem menos. O topo, pequeno, é o E2E.
Diagrama 1 — A pirâmide clássica (de baixo pra cima)
Lê-se de baixo (base, muitos) para cima (topo, poucos). A largura representa a quantidade; a altura, o custo e a lentidão.
flowchart BT U["Unitários<br/>MUITOS · rápidos · baratos<br/>base larga e estável"] I["Integração<br/>ALGUNS · médios<br/>testam colaboração"] E["E2E<br/>POUCOS · lentos · caros<br/>topo estreito"] U --> I --> E classDef base fill:#d5f5e3,stroke:#27ae60,color:#000 classDef mid fill:#fcf3cf,stroke:#f1c40f,color:#000 classDef top fill:#fadbd8,stroke:#e74c3c,color:#000 class U base class I mid class E top
Leitura do diagrama: a seta sobe Unitários → Integração → E2E. Quanto mais você sobe, menos testes você quer, porque cada um custa mais para escrever, roda mais devagar e quebra mais fácil. A base larga garante feedback rápido; o topo estreito garante que você não afunde em E2E frágil.
De onde veio a pirâmide
A ideia foi esboçada por Cohn em conversas por volta de 2003-2004 e formalizada no livro de 2009. Jason Huggins chegou à mesma intuição de forma independente. Fowler é a fonte mais linkada hoje, com o gráfico canônico.
A pirâmide funciona muito bem para backend clássico — uma API Java/Spring com lógica de negócio rica, onde a maior parte do que pode dar errado é regra de domínio testável em unit. Vale a pena cruzar com Testes em Java para ver isso na prática.
O Testing Trophy (Kent C. Dodds)
Agora a variação que nasceu do desconforto de quem vive no frontend. Kent C. Dodds propôs o Testing Trophy — um troféu, não uma pirâmide — com quatro camadas e um lema famoso: “Write tests. Not too many. Mostly integration.” (Escreva testes. Não muitos. A maioria de integração.)
Por que a forma muda? Dois motivos.
Primeiro: a base de análise estática. O troféu acrescenta um andar que a pirâmide ignora — análise estática: o compilador de tipos (TypeScript), o linter (ESLint), o validador de schema (Zod). Em React + TS, o compilador pega uma classe inteira de bugs (undefined is not a function, prop com tipo errado, import quebrado) antes de qualquer teste rodar. É o teste mais barato que existe: você nem escreve, ele já está lá. Por isso é a base larga do troféu.
Segundo: integração no centro de gravidade. No frontend, testar uma função pura isolada raramente captura o bug real — o bug mora na interação entre componente, estado, evento e DOM. Um teste de integração com Testing Library (“renderiza o formulário, preenche, clica, verifica o resultado”) dá muito mais confiança por linha escrita, sem depender de detalhes de implementação. Por isso o troféu engorda no meio, não na base de unit.
Diagrama 2 — O Testing Trophy (de baixo pra cima)
Quatro andares. A base agora é análise estática, e o miolo gordo é integração — não unit.
flowchart BT S["Análise estática<br/>TypeScript · ESLint · Zod<br/>de graça, antes de rodar"] U["Unitários<br/>lógica pura complexa"] I["Integração<br/>O CENTRO DE GRAVIDADE<br/>componentes colaborando"] E["E2E<br/>jornadas críticas"] S --> U --> I --> E classDef est fill:#d6eaf8,stroke:#2980b9,color:#000 classDef base fill:#d5f5e3,stroke:#27ae60,color:#000 classDef mid fill:#fcf3cf,stroke:#f1c40f,color:#000 classDef top fill:#fadbd8,stroke:#e74c3c,color:#000 class S est class U base class I mid class E top
Leitura do diagrama: a base Análise estática é o piso de graça. Subindo, Unitários para lógica pura, depois o andar mais largo, Integração, onde o troféu concentra o investimento, e no topo E2E para as jornadas que não podem quebrar. O retorno sobre investimento (ROI) é máximo no meio.
O troféu é o modelo natural para quem trabalha com Testes em JavaScript — frontend React + TypeScript + Testing Library. Não porque a pirâmide “esteja errada”, mas porque o risco mora em outro lugar: muito do que quebraria em unit no backend, no frontend já é pego pelo compilador ou só aparece na colaboração.
Pirâmide × Troféu não é guerra
São o mesmo princípio — “concentre testes onde o risco é alto e o custo é baixo” — aplicado a sistemas com perfis de risco diferentes. Backend rico em lógica de domínio puxa pra base de unit. Frontend rico em interação e tipagem puxa pra base estática e ao miolo de integração. Escolher o modelo é diagnosticar o sistema, não torcer por um time.
Os anti-padrões: quando a forma denuncia o problema
Há duas silhuetas que, quando você as vê, sabe que alguém está sofrendo. Elas são úteis justamente como alarme: a forma errada não causa a dor, mas é o sintoma visível dela.
A ampulheta (hourglass)
Muito unit, muito E2E, pouca integração — o miolo afundou. Parece responsável (olha, temos cobertura embaixo e em cima!), mas é uma armadilha. A camada que mais barato compra confiança realista — a integração — é exatamente a que falta. O resultado: unit verde, E2E verde, e mesmo assim bugs de fiação escapam (o controller chama o service errado, o mapeamento ORM quebra), porque ninguém testou as camadas conversando. Quando o E2E enfim pega, é caro e impreciso.
O cone de sorvete (ice-cream cone)
A pirâmide de cabeça pra baixo. Topo gigante (muito E2E, frequentemente manual), base raquítica (quase nenhum unit). O Google chamou isso de anti-padrão em 2015. A imagem: uma casquinha equilibrando uma bola de sorvete enorme — instável, derretendo. Toda a confiança depende de testes lentos, frágeis e caros; o feedback chega dias depois, quando um humano clica num fluxo e descobre que algo quebrou lá atrás. Caríssimo de manter, pesadelo de debugar.
Diagrama 3 — Os dois anti-padrões
À esquerda a ampulheta (miolo afundado); à direita o cone de sorvete (invertido, topo pesado).
flowchart TB subgraph AMP["AMPULHETA — miolo afundado"] direction BT AU["Unitários<br/>MUITOS"] AI["Integração<br/>quase nada — o buraco"] AE["E2E<br/>MUITOS"] AU --> AI --> AE end subgraph CONE["CONE DE SORVETE — invertido"] direction BT CU["Unitários<br/>quase nada"] CI["Integração<br/>pouco"] CE["E2E manual<br/>ENORME · lento · frágil"] CU --> CI --> CE end classDef bad fill:#fadbd8,stroke:#e74c3c,color:#000 classDef gap fill:#f5b7b1,stroke:#c0392b,color:#000,stroke-dasharray: 5 5 class AU,AE,CU,CI,CE bad class AI gap
Leitura do diagrama: na ampulheta, Integração (tracejada) é o buraco no meio — os bugs de colaboração caem nesse vão e só aparecem no E2E caro. No cone, o peso está todo em cima, em E2E manual, sobre uma base inexistente — a suíte inteira é lenta e quebradiça. Ambas falham pela mesma razão: o investimento não está onde o risco é barato de cobrir.
A ampulheta tem uma defesa parcial
Em código legado sem costuras pra testar em unit, e com ferramentas de E2E mais robustas hoje, alguns defendem a ampulheta como mal menor temporário — você empurra parte da verificação de integração pelo topo. É uma concessão à realidade, não um ideal. Não comece um projeto novo mirando a ampulheta.
A pergunta que substitui o dogma
Aqui está o pulo do gato senior. Esqueça a proporção decorada. A proporção não é regra — é consequência. A pergunta operacional é uma só:
A pergunta de ouro
“Qual teste eu quero que FALHE quando ESTE bug aparecer?”
Cada bug que você teme tem um nível natural onde ele se manifesta. Deixe o bug escolher o teste, e a forma da suíte emerge sozinha.
- Bug em lógica isolada (cálculo de juros errado, parser que engasga num edge case)? Você quer um unit falhando. É barato e aponta direto.
- Bug na fiação controller → service → repositório → banco (transação não commita, mapeamento ORM perde um campo)? Nenhum unit pega isso — você quer um teste de integração falhando.
- Bug num fluxo crítico de usuário (checkout que não fecha o pedido, login que não autentica)? Você quer um E2E falhando, porque o risco é o sistema inteiro não entregar valor.
Diagrama 4 — Qual teste para qual bug
Um fluxograma de decisão. Comece pela natureza do bug e siga até o nível.
flowchart TD START["Surgiu um bug.<br/>Onde ele mora?"] Q1{"É lógica pura,<br/>isolada?"} Q2{"É fiação entre<br/>camadas / contrato<br/>com banco/fila/HTTP?"} Q3{"É um fluxo crítico<br/>de ponta a ponta<br/>pelo olhar do usuário?"} UNIT["Teste UNITÁRIO<br/>barato, preciso, rápido"] INT["Teste de INTEGRAÇÃO<br/>colaboração real"] E2E["Teste E2E<br/>caro, mas insubstituível aqui"] REV["Talvez não precise de teste<br/>— ou é caso de análise estática"] START --> Q1 Q1 -- sim --> UNIT Q1 -- não --> Q2 Q2 -- sim --> INT Q2 -- não --> Q3 Q3 -- sim --> E2E Q3 -- não --> REV classDef start fill:#d6eaf8,stroke:#2980b9,color:#000 classDef dec fill:#fcf3cf,stroke:#f1c40f,color:#000 classDef leaf fill:#d5f5e3,stroke:#27ae60,color:#000 class START start class Q1,Q2,Q3 dec class UNIT,INT,E2E,REV leaf
Leitura do diagrama: você desce pelas perguntas. É lógica pura? → unit. Senão, é fiação/contrato? → integração. Senão, é fluxo crítico? → E2E. Se nada disso, talvez o caso seja análise estática ou nem precise de teste. A proporção final da suíte é só a soma dessas decisões individuais — ela emerge do risco, não é imposta de fora.
Proporção por contexto
Juntando tudo: a “forma certa” depende de onde seu sistema concentra risco.
- Backend Java/Spring puro — lógica de domínio rica, muitos branches → pirâmide clássica. Base gorda de unit, integração testando repositórios e endpoints, E2E só nos fluxos-chave.
- Frontend React + TS + Testing Library — risco em interação e tipos → Testing Trophy. Base de análise estática, miolo de integração, unit só pra lógica pura complexa.
- Lógica algorítmica pesada (parsers, engines de regra, cálculo) → quase tudo unit. A pirâmide vira quase um pilar: o risco é matemático e isolado, perfeito pra unit barato e exaustivo.
- Microserviços com contratos entre si → adicione uma camada que nenhum dos modelos clássicos enfatiza: contract tests. Você não quer subir todos os serviços num E2E gigante pra descobrir que o serviço A mudou o JSON que o serviço B consome — um contract test pega isso barato, de cada lado. Esse e outros tipos especiais ficam em 13 - Além do básico - property-based, snapshot, contract, smoke.
A meta-regra
Não existe proporção universal porque não existe sistema universal. A pirâmide é o default sensato para a maioria dos backends; o troféu, para frontends ricos. Mas o que você está realmente fazendo é responder, bug a bug, “qual teste eu quero que falhe?” — e deixar a silhueta se formar. Quem decora a proporção tropeça quando o contexto muda; quem entende o princípio se adapta.
Em entrevista
Use estas frases para mostrar que você pensa em risco, não em dogma.
The test pyramid suggests many fast unit tests at the base and few slow E2E tests at the top, balancing confidence against speed and cost. Kent C. Dodds’ Testing Trophy adapts this for frontend by adding static analysis at the base and shifting the center of gravity to integration tests. I treat the ratio as a consequence, not a rule: I ask “which test do I want to fail when this specific bug appears?” — isolated logic calls for a unit test, cross-layer wiring for an integration test, a critical user journey for E2E. I watch for two anti-patterns: the ice-cream cone, where heavy manual E2E sits on almost no unit tests, and the hourglass, where the integration middle has collapsed. For a Spring backend I lean toward the classic pyramid, while for a React and TypeScript app the trophy fits better because the compiler already catches a whole class of bugs for free. With microservices I add contract tests so I don’t need a giant end-to-end suite just to catch a broken JSON contract.
Vocabulário
- pirâmide de testes → test pyramid
- forma da suíte → shape of the suite
- centro de gravidade → center of gravity
- detalhes de implementação → implementation details
- retorno sobre investimento → return on investment (ROI)
- análise estática → static analysis
- cone de sorvete → ice-cream cone
- ampulheta → hourglass
- fiação entre camadas → cross-layer wiring
- jornada crítica do usuário → critical user journey
- testes de contrato → contract tests
- ciclo de feedback → feedback loop
Lastro
- Martin Fowler, The Practical Test Pyramid (martinfowler.com/articles/practical-test-pyramid.html) e o verbete TestPyramid (martinfowler.com/bliki/TestPyramid.html) — origem em Mike Cohn, Succeeding with Agile (2009).
- Kent C. Dodds, Static vs Unit vs Integration vs E2E Testing for Frontend Apps e Write tests. Not too many. Mostly integration. (kentcdodds.com) — o Testing Trophy e a base de análise estática.
- Discussões do anti-padrão ice-cream cone (rotulado pelo Google em 2015) e da hourglass — ex.: Octomind, “Testing Pyramid: an evolutionary tale”, e Carolina Ramirez, “Testing Anti-Patterns” (Geek Culture / Medium).
Veja também
- 01 - O que são testes e por que testar — o porquê, antes da forma
- 04 - Testes unitários — a base larga em detalhe
- 07 - Testes de integração — o miolo que os anti-padrões esquecem
- 13 - Além do básico - property-based, snapshot, contract, smoke — contract tests e os tipos especiais por contexto
- 16 - Estratégia de testes em entrevista — como defender sua forma de suíte
- Testes — índice do galho