Testes unitários, de integração e ponta a ponta
Os níveis de teste, o que cada um garante e como equilibrar custo e confiança.
Para que servem os testes
Todo desenvolvedor testa o próprio código: roda a aplicação, clica, confere o resultado. O problema é que esse teste manual acontece uma vez, cobre o que a pessoa lembrou de conferir e não se repete quando alguém muda o código seis meses depois.
Um teste automatizado é um programa que executa uma parte do sistema e confere o resultado. Ele roda em segundos, sempre do mesmo jeito, a cada mudança. Os ganhos:
- Confiança para mudar: uma refatoração que quebra algo é detectada antes do deploy.
- Documentação executável: o teste mostra, com exemplos, o que o código deve fazer.
- Retorno rápido: o erro aparece minutos depois de escrito, e não em produção.
- Revisão de código gerado: quando uma IA escreve a função, os testes dizem se ela faz o que foi pedido.
O último ponto pesa cada vez mais. Ler código gerado e achar que está certo é diferente de comprovar que está certo. Um teste que exercita os casos importantes, inclusive os de erro, é a forma mais barata de comprovar.
A anatomia de um teste
Quase todo teste tem três partes, conhecidas como preparar, agir, conferir:
it("aplica 10% de desconto limitado a R$ 50", () => {
// Preparar: os dados de entrada
const totalEmCentavos = 80_000; // R$ 800,00
// Agir: executa o que está sendo testado
const desconto = calcularDesconto(totalEmCentavos, "DEZ");
// Conferir: o resultado é o esperado?
expect(desconto).toBe(5_000); // R$ 50,00, e não R$ 80,00
});O nome do teste descreve o comportamento, e não a implementação. Quando ele falha, o nome já diz o que quebrou.
Os três níveis
| Nível | O que testa | Dependências | Velocidade | Quando falha, aponta para |
|---|---|---|---|---|
| Unitário | Uma função ou classe, isolada | Nenhuma real: banco e serviços são simulados ou não existem | Milissegundos | Uma função específica |
| Integração | Várias partes juntas: código e banco, código e fila | Reais, ou muito próximas das reais | Segundos | Uma combinação de partes |
| Ponta a ponta | O sistema inteiro, como o usuário usa | Todas, com navegador ou chamadas HTTP reais | Segundos a minutos | Algum lugar do caminho inteiro |
Unitário
Testa uma regra de negócio, um cálculo, uma validação, sem banco, rede ou arquivo. Por isso roda em milissegundos, e é possível ter milhares deles. É o nível certo para regras com muitos casos: impostos, descontos, prazos, permissões.
Integração
Testa se as partes conversam corretamente: se a consulta SQL funciona no banco de verdade, se a mensagem publicada chega à fila, se a rota HTTP grava o que deveria. Pega os erros que moram entre as partes, que um teste unitário não alcança por definição.
Ponta a ponta
Testa um fluxo completo pela interface que o usuário usa: abre o navegador, faz login, adiciona ao carrinho, finaliza a compra. É o que mais se parece com o uso real, e também o mais lento, o mais caro de manter e o mais sujeito a falhar por motivos alheios ao código.
O que um teste simulado não vê
Para isolar o código, os testes unitários costumam substituir o banco por um dublê: um objeto falso que devolve respostas prontas. Isso deixa o teste rápido, mas cria um ponto cego: o teste confere o código contra a ideia que você tem do banco, e não contra o banco.
Reproduzimos esse ponto cego com uma função que cadastra um cliente e devolve null se o e-mail já existir, usando INSERT ... ON CONFLICT (email) DO NOTHING.
| Teste | Resultado |
|---|---|
| Unitário, com o banco simulado, para cliente novo | Passou |
| Unitário, com o banco simulado, para e-mail repetido | Passou |
| Integração, num PostgreSQL real, com a migração do projeto | Falhou |
O erro do PostgreSQL: não existe uma restrição única que corresponda ao ON CONFLICT. A migração tinha esquecido o UNIQUE na coluna email. Os testes com o banco simulado nunca perceberiam, porque o dublê devolve o que mandaram ele devolver. Com o UNIQUE na migração, o teste de integração passou.
A pirâmide
A recomendação clássica é ter muitos testes unitários, alguns de integração e poucos de ponta a ponta, formando uma pirâmide:
/\ poucos de ponta a ponta: fluxos críticos
/ \
/----\ alguns de integração: banco, filas, APIs
/ \
/--------\ muitos unitários: regras de negócioA lógica é de custo. Quanto mais alto o nível, mais lento, mais caro de escrever, mais difícil de diagnosticar quando falha. Mas quanto mais alto, mais confiança ele dá de que o sistema funciona de verdade.
A pirâmide é um ponto de partida, e não uma lei. Num backend que é principalmente consultas ao banco e pouca regra de negócio, os testes de integração rendem mais que centenas de testes unitários com o banco simulado. O nível Soluções discute como equilibrar.
Por que isso importa quando a IA escreve o código
Assistentes escrevem testes com facilidade, inclusive quando a única instrução é "crie testes para esta função". Os testes gerados costumam ter estes problemas:
- Conferem que a função foi chamada, e não que o resultado está certo.
- Simulam tudo, inclusive o banco, e passam com uma consulta que falharia de verdade.
- Cobrem só o caminho feliz, sem entradas inválidas, limites e erros.
- Repetem a lógica da implementação no próprio teste, e passam mesmo com a lógica errada.
- São ajustados até passar quando falham, em vez de revelar o defeito no código.
O último é o mais perigoso: um teste que falha está dando uma informação. Afrouxar o teste para ele passar esconde o defeito que ele encontrou.
O próximo nível compara as estratégias de teste e mostra como escolher o que testar em cada nível.
Prompts para aprofundar
Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.
Testes em cinco minutos
Explique em no máximo cinco minutos de leitura o que são testes automatizados e os três níveis de teste, para alguém que programa mas sempre testou clicando na aplicação. Cubra só o essencial: para que servem os testes, a diferença entre unitário, integração e ponta a ponta, e por que um teste com o banco simulado pode passar enquanto o código real falha. Use o exemplo de uma regra de desconto numa loja virtual. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
Os três níveis com o mesmo exemplo
Quero entender os níveis de teste vendo a mesma funcionalidade testada em cada um. Aja como um mentor de backend. Pergunte primeiro qual é a minha experiência com testes e em qual linguagem eu trabalho. Use como exemplo a aplicação de um cupom de desconto numa compra. Conduza assim, uma etapa por vez: 1. Mostre um teste unitário da regra de desconto e pergunte o que ele garante e o que não garante. 2. Mostre um teste de integração que grava o pedido com desconto no banco e pergunte que tipo de defeito ele pega que o unitário não pega. 3. Mostre um teste de ponta a ponta do fluxo de compra com cupom e pergunte por que não vale a pena testar todas as variações nesse nível. 4. Explique a pirâmide de testes e quando o formato de troféu faz mais sentido. Espere a minha resposta em cada etapa antes de continuar.
O que torna um teste bom
Quero aprender a distinguir um teste bom de um teste que só parece bom. Explique as características de um bom teste: confere o comportamento e não a implementação, falha quando o comportamento quebra e só nesse caso, é independente dos outros, é rápido e tem um nome que descreve o comportamento. Depois me mostre oito testes, um por vez, e pergunte o que há de errado em cada um, se houver. Inclua: - um teste sem nenhuma conferência; - um que repete a fórmula da implementação no valor esperado; - um que só confere que uma função foi chamada; - um que depende da data de hoje; - um que depende de outro teste ter rodado antes; - um com uma espera fixa; - dois testes corretos. Corrija cada resposta e mostre a versão melhorada.
Quando o dublê engana
Quero entender os dublês de teste e o ponto cego que eles criam. Explique a diferença entre stub, mock, fake e spy, com um exemplo de cada. Depois mostre um caso concreto: uma função que usa INSERT com ON CONFLICT numa coluna sem restrição única. Mostre um teste unitário com o banco simulado que passa e um teste de integração com o banco real que falha, e explique por quê. Em seguida, me apresente seis dependências (banco de dados próprio, gateway de pagamento, envio de e-mail, relógio do sistema, uma biblioteca de cálculo de impostos, um serviço interno de outra equipe) e pergunte, para cada uma, se eu substituiria por um dublê nos testes e em qual nível. Corrija as minhas respostas.