Test-Driven Development (TDD)
Um guia completo sobre o que é TDD, como funciona o ciclo Red-Green-Refactor e como aplicá-lo para desenvolver software com mais confiança e menos retrabalho.
O que é TDD?
TDD (Test-Driven Development), ou Desenvolvimento Guiado por Testes, é uma prática em que você escreve o teste antes de escrever o código que o faz passar. A ideia inverte a ordem intuitiva: em vez de programar a funcionalidade e depois testá-la, você primeiro define o que o código deveria fazer na forma de um teste automatizado.
A diferença entre TDD e escrever testes é o momento e a intenção: no TDD o teste é um ato de design, não de verificação posterior. O teste descreve o comportamento esperado antes da implementação existir, e é ele quem guia como o código vai nascer.
Analogia simples: escrever o teste primeiro é como definir o critério de aprovação de uma prova antes de estudar. Você sabe exatamente o que precisa ser verdade no final, e cada linha de código escrita tem um objetivo claro: fazer aquele teste passar.
Por que usar TDD?
| Sem TDD | Com TDD |
|---|---|
| Testes escritos depois (ou nunca) | Comportamento definido antes do código |
| "Acho que funciona" | Garantia verificável de que funciona |
| Medo de refatorar | Refatoração segura com rede de proteção |
| Bugs descobertos em produção | Bugs descobertos no momento da escrita |
| Design acoplado e difícil de testar | Design naturalmente modular e testável |
O ciclo Red-Green-Refactor
O coração do TDD é um ciclo curto e repetitivo de três passos:
🔴 RED — Escreva um teste que falha
Escreva um teste para um comportamento que ainda não existe. Ele deve falhar — afinal, o código ainda não foi escrito. Esse passo força você a definir, antes de programar, o que exatamente é o sucesso.
🟢 GREEN — Faça o teste passar
Escreva a solução mais simples possível para o teste passar. Sem elegância, sem generalizações prematuras. O objetivo aqui é único: sair do vermelho para o verde o mais rápido possível.
🔵 REFACTOR — Limpe o código
Com os testes passando, melhore o código: remova duplicação, melhore nomes, simplifique a estrutura. A segurança vem dos testes — se algo quebrar durante a refatoração, eles avisam imediatamente.
Em seguida, volte ao RED com o próximo comportamento. Os ciclos são curtos, geralmente de minutos.
Anatomia de um bom teste
Um teste de unidade bem escrito costuma seguir o padrão AAA — Arrange, Act, Assert. Exemplo de guia em typescript:
describe('calcularNivelDeRisco', () => {
it('retorna "alto" quando há 3 ou mais ocorrências na zona', () => {
// Arrange — prepara os dados de entrada
const ocorrencias = [criarOcorrencia(), criarOcorrencia(), criarOcorrencia()];
// Act — executa o comportamento sob teste
const nivel = calcularNivelDeRisco(ocorrencias);
// Assert — verifica o resultado esperado
expect(nivel).toBe('alto');
});
});
Características de um bom teste:
- Determinístico — sempre dá o mesmo resultado, sem depender de horário, rede ou ordem de execução.
- Isolado — testa uma unidade por vez; dependências externas são simuladas (mocks/stubs).
- Legível — o nome do teste descreve o comportamento, não a implementação.
- Rápido — roda em milissegundos, para permitir ciclos curtos.
Os níveis da pirâmide de testes
TDD trabalha principalmente no nível dos testes de unidade, mas vale entender onde cada tipo se encaixa:
| Nível | O que testa | Quantidade | Velocidade |
|---|---|---|---|
| Unidade | Uma função ou componente isolado | Muitos | Muito rápida |
| Integração | Vários módulos trabalhando juntos | Alguns | Média |
| End-to-end (E2E) | O fluxo completo do usuário | Poucos | Lenta |
A base larga (muitos testes de unidade) é o que torna o TDD viável: testes rápidos permitem o ciclo Red-Green-Refactor sem fricção.
Como aplicar TDD na prática
- Escolha o menor comportamento testável. Não tente cobrir a feature inteira de uma vez; pegue um caso específico.
- Escreva o teste e veja-o falhar (RED). Confirme que ele falha pelo motivo certo — isso valida que o teste de fato testa algo.
- Implemente o mínimo para passar (GREEN). Resista à tentação de adiantar funcionalidades futuras.
- Refatore (REFACTOR). Melhore o código de produção e os próprios testes, mantendo tudo verde.
- Repita. Adicione o próximo caso de borda como um novo teste e recomece o ciclo.
TDD com IA
A IA potencializa o TDD quando usada na ordem certa. Em vez de pedir "escreva essa funcionalidade", você usa a IA dentro do ciclo:
Para gerar os testes a partir de uma spec:
"Com base nesta spec [cola a spec da feature], escreva testes de unidade cobrindo todos os critérios de aceite. Não implemente o código ainda."
Para implementar (passar do RED para o GREEN):
"Aqui estão os testes que devem passar [cola os testes]. Implemente a solução mais simples que faça todos passarem, sem adicionar comportamento extra."
Para refatorar com segurança:
"O código abaixo está com os testes passando [cola código e testes]. Sugira refatorações que melhorem a legibilidade mantendo todos os testes verdes."
Para encontrar casos de borda esquecidos:
"Analise esta função e seus testes [cola]. Que casos de borda ainda não estão cobertos?"
Atenção: sempre rode os testes você mesmo. A IA pode gerar um teste que "passa" mas não valida o comportamento real, ou afirmar que algo passa sem ter executado. No TDD, quem decide é o terminal verde, não a IA.
Erros comuns ao fazer TDD
| Erro | Problema | Correção |
|---|---|---|
| Escrever o código antes do teste | Perde o benefício de design antecipado | Sempre teste primeiro — RED antes de GREEN |
| Testar a implementação, não o comportamento | Testes quebram a cada refatoração | Teste o que o código faz, não como |
| Pular o passo de refatoração | Código fica bagunçado com o tempo | Refatore a cada ciclo, com os testes como rede |
| Testes lentos ou dependentes de rede | Quebra o ritmo do ciclo curto | Use mocks/stubs e mantenha testes isolados |
| Teste que nunca falhou | Pode estar verificando nada | Veja o teste falhar antes de fazê-lo passar |
Como organizar os testes no projeto
src/
├── components/
│ ├── NewsFeed.tsx
│ └── NewsFeed.test.tsx
├── lib/
│ ├── risco.ts
│ └── risco.test.ts
└── ...
Dica: mantenha os testes ao lado do código que eles testam (ou em uma pasta __tests__/ próxima). Isso facilita encontrá-los, mantê-los atualizados e garante que passem pelo mesmo code review e versionamento do código de produção.
TDD vs outras abordagens
| Abordagem | Quando usar | Limitação |
|---|---|---|
| TDD | Quando os comportamentos são bem definidos | Testes não substituem o design de arquitetura |
| Spec-Driven | Projetos estruturados, equipes, uso intenso de IA | Exige disciplina para manter specs atualizadas |
| BDD | Quando o negócio precisa validar o comportamento | Verbosidade pode atrapalhar em times pequenos |
| Ad hoc | Prototipagem rápida, projetos solo | Não escala, gera retrabalho com o tempo |
TDD e Spec-Driven Development se complementam bem: a spec define o que construir e os critérios de aceite; o TDD transforma esses critérios em testes que guiam e garantem a implementação.
Tecnologias para o SafeStreets
| Camada | Stack | Ferramentas de teste sugeridas |
|---|---|---|
| Frontend (Next.js / TypeScript) | React | Vitest ou Jest + React Testing Library |
| Backend (FastAPI / Python) | Python | pytest |
| Fluxo E2E (opcional) | App completo | Playwright ou Cypress |
Origem do TDD: a prática foi popularizada por Kent Beck como parte da metodologia Extreme Programming (XP), e detalhada em seu livro Test-Driven Development: By Example (2002). As ferramentas centrais do TDD são os testes de unidade.