TDD: Como Aplicar Test-Driven Development na Prática
Aprenda TDD na prática: ciclo red, green e refactor, testes unitários, doubles, código legado, CI e exemplos para desenvolver com feedback rápido.

Test-Driven Development (TDD) é uma forma de desenvolver software em ciclos curtos: primeiro você descreve um comportamento com um teste que falha, depois escreve o mínimo de código para fazê-lo passar e, por fim, melhora o design sem alterar o resultado.
O teste não aparece no final como uma inspeção; ele participa da descoberta da solução.
A prática parece simples, mas exige disciplina para escolher exemplos pequenos, interpretar falhas e refatorar continuamente.
Quando funciona, o retorno vem em forma de feedback rápido, regressões detectadas cedo e código mais fácil de modificar.
Quando vira uma busca cega por cobertura, produz testes frágeis e pouco valor.
Neste guia, você verá como executar o ciclo red, green e refactor, construir um primeiro exemplo, lidar com dependências, aplicar TDD em legado e conectar a suíte à integração contínua.
O foco é a técnica, independentemente de linguagem ou framework.
O que é Test-Driven Development?
Test-Driven Development significa desenvolvimento orientado por testes. A Agile Alliance o descreve como uma combinação estreita de programação, testes unitários e design por refatoração.
A ordem importa: um teste de comportamento vem antes do código de produção correspondente.
O objetivo imediato é obter um sinal confiável sobre uma pequena decisão. Ao repetir o processo, a suíte registra exemplos do domínio e cria uma rede de segurança.
TDD combina especialmente bem com métodos ágeis, porque reduz o tamanho dos lotes e encurta a distância entre decisão e feedback.
O ciclo red, green e refactor
O ciclo tem três estados, não três fases longas. Um bom ritmo pode durar minutos.
Red confirma que o teste é capaz de detectar a ausência do comportamento; green confirma a implementação mínima; refactor remove duplicação e melhora nomes, limites e estrutura. Martin Fowler resume a prática como escrever um teste que falha, fazê-lo passar e melhorar o código.
Red: descreva um comportamento e veja a falha
Escreva um exemplo observável, execute-o e leia a mensagem. A falha deve ocorrer pelo motivo esperado: função ausente, resultado incorreto ou regra ainda não implementada.
Se falhar por import quebrado, configuração ou dado inválido, resolva esse ruído antes. Um teste que já nasce verde pode estar exercitando código antigo, não possuir asserção ou descrever algo que já existe.
Green: escreva apenas o necessário
Implemente a solução mais simples que atende ao exemplo atual. “Mínimo” não significa deliberadamente ruim: significa evitar abstrações que nenhum comportamento pediu.
Rode o teste, depois a suíte curta relacionada. Se algo não relacionado quebrar, investigue antes de avançar. O green deve ser pequeno o suficiente para que você entenda por que o resultado mudou.
Refactor: melhore o design com segurança
Com tudo verde, elimine duplicação, dê nomes mais expressivos, extraia funções e reorganize responsabilidades. O comportamento externo permanece igual.
Faça uma transformação por vez e execute os testes. As mesmas ideias de legibilidade presentes em boas práticas de código limpo tornam a etapa mais objetiva.
TDD não é apenas automatizar testes
Testar depois pode verificar uma implementação concluída; TDD influencia como ela nasce. Para escrever o teste antes, você precisa definir uma interface utilizável, separar o comportamento de detalhes externos e tornar resultados observáveis.
Essa pressão de design é parte do valor.
Também não é garantia matemática de ausência de defeitos. Cada teste cobre exemplos escolhidos, e requisitos podem estar errados. Testes exploratórios, integração, contrato, segurança, desempenho e revisão continuam necessários.
TDD ocupa a camada de feedback rápido do desenvolvedor, não substitui toda a estratégia de qualidade.

Exemplo prático de TDD com TypeScript e Jest
Considere uma função que calcula frete. Pedidos de R$ 200 ou mais têm frete grátis; abaixo desse valor, o frete padrão custa R$ 20. O exemplo usa Jest, cujo guia oficial apresenta a estrutura básica de testes e asserções.
Primeiro comportamento: frete grátis
describe('calcularFrete', () => {
it('retorna zero para pedido a partir de 200 reais', () => {
expect(calcularFrete(200)).toBe(0)
})
})
Execute e confirme o red. Em seguida, crie a implementação mínima:
export function calcularFrete(total: number): number {
return 0
}
O retorno constante é aceitável por alguns segundos: ele prova que o primeiro exemplo não basta para revelar a regra completa. Essa lacuna orienta o próximo teste.
Triangulação: adicione um segundo exemplo
it('cobra 20 reais abaixo do limite', () => {
expect(calcularFrete(199.99)).toBe(20)
})
Agora o teste falha pelo motivo certo. A implementação evolui apenas o necessário:
export function calcularFrete(total: number): number {
if (total >= 200) return 0
return 20
}
Antes de seguir, pergunte sobre valores negativos, moeda, arredondamento e políticas por região. Nem toda possibilidade precisa virar teste; priorize regras relevantes e limites propensos a erro.
Triangulação é o uso de novos exemplos para forçar uma solução mais geral sem antecipar arquitetura.
Como reconhecer um bom teste unitário
- Rápido: roda com frequência e não desestimula o ciclo.
- Determinístico: produz o mesmo resultado nas mesmas condições.
- Isolado: uma falha aponta para uma região pequena do comportamento.
- Legível: nome, dados e asserção contam uma regra do domínio.
- Relevante: protege uma decisão, limite ou risco real.
Um teste pode ter poucas linhas e ainda ser ruim se repetir detalhes internos. Prefira observar saídas, efeitos e contratos públicos. Isso permite refatorar a implementação sem reescrever a suíte.
Arrange, act e assert
A estrutura arrange-act-assert separa preparação, ação e verificação. Prepare somente os dados essenciais, execute uma ação principal e verifique o resultado importante.
Várias asserções podem ser adequadas quando descrevem um único comportamento; o problema é misturar cenários independentes, pois a primeira falha esconde as demais.
Test doubles: stubs, fakes e mocks
Dependências lentas ou não determinísticas pedem substitutos controláveis. Um stub fornece respostas predefinidas; um fake possui implementação simplificada, como um repositório em memória; um mock verifica uma interação esperada.
Use o termo com precisão, mas escolha o double pela necessidade do teste.
Uma função que decide aprovar pagamento pode receber um gateway por interface. No teste, um stub devolve “aprovado” sem rede. Para confirmar que uma notificação foi solicitada, um spy registra a chamada. Essa separação também favorece uma arquitetura de APIs bem definida.
Quando não usar mocks
Não simule cada classe interna. Testes que verificam sequência de métodos privados quebram em refatorações inofensivas e acoplam a suíte à implementação.
Para objetos de valor e cálculos puros, use instâncias reais. Reserve doubles para limites externos, tempo, aleatoriedade ou efeitos difíceis de controlar.
O que TDD revela sobre o design
Se um comportamento exige dezenas de dependências para ser testado, o design pode concentrar responsabilidades demais. Se o resultado é impossível de observar sem acessar campos privados, o contrato pode estar mal definido.
Se o teste demanda rede para uma regra de negócio, detalhes de infraestrutura talvez estejam misturados ao domínio.
Esses sinais não prescrevem automaticamente uma solução. Eles iniciam perguntas: qual é a responsabilidade desta unidade? Que dependência deveria entrar por parâmetro? A interface expressa intenção? TDD melhora design quando a equipe responde a esse feedback na etapa de refatoração.
Cobertura alta não garante qualidade
Cobertura indica quais linhas ou ramos foram executados, não se as asserções são úteis. Um teste sem verificação pode elevar o número; uma regra crítica pode ter exemplos insuficientes.
Use cobertura para encontrar áreas não exercitadas, jamais como prova isolada de qualidade.
Uma meta rígida também incentiva testes de getters, configurações triviais e detalhes gerados.
É melhor proteger decisões de negócio, bordas de entrada, correções de defeitos e módulos de alta mudança. Combine cobertura com mutação, tempo da suíte, taxa de testes instáveis e defeitos que escapam.
TDD, teste unitário e BDD: diferenças
Teste unitário é um tipo de teste; TDD é um processo de desenvolvimento. Você pode criar testes unitários depois do código sem praticar TDD.
BDD amplia a conversa sobre comportamento com exemplos compreensíveis por negócio e tecnologia. As práticas podem coexistir: exemplos de BDD orientam critérios de aceitação, enquanto ciclos de TDD conduzem unidades internas.
O tamanho da unidade também não precisa corresponder a uma classe. Pode ser uma função, um conjunto pequeno de objetos ou uma regra observada por uma API interna.
A unidade adequada é a menor fronteira que oferece feedback útil sem acoplamento excessivo.
Como testar banco, HTTP, tempo e filas
Separe regra de negócio de adaptadores. O domínio recebe uma porta para persistência ou comunicação; testes rápidos usam um fake ou stub; testes de integração exercitam o adaptador real em menor quantidade. Para tempo, injete um relógio; para aleatoriedade, uma fonte controlável; para filas, valide publicação e consumo em níveis distintos.
Não substitua todo teste integrado por mocks. SQL, serialização, cabeçalhos e contratos HTTP possuem riscos que só aparecem com componentes reais. Em Java, o guia de JUnit cobre recursos de execução e organização; para um exemplo aplicado, veja também testes unitários em Java com JUnit.
TDD em código legado
Em legado, começar por uma nova regra pode ser difícil porque o módulo mistura banco, arquivos, estado global e lógica. Evite uma grande reescrita. Escolha uma alteração pequena, encontre um ponto observável e crie uma primeira proteção.
Às vezes será necessário um teste de integração mais amplo antes de alcançar unidades menores.
Testes de caracterização
Um teste de caracterização registra o comportamento atual, inclusive peculiaridades. Primeiro observe entradas e saídas reais; depois transforme a observação em um teste repetível.
Ele não declara que o comportamento é ideal, mas avisa se a refatoração o altera. Quando houver uma decisão de negócio, mude o teste e o código conscientemente.
Refatoração protegida por testes
Crie um ponto de separação, mova uma responsabilidade e rode a suíte. Repita. Commits pequenos facilitam comparar e reverter; um bom fluxo com Git e GitHub preserva a rastreabilidade.
Só introduza novas abstrações quando elas reduzirem uma dor demonstrada.

TDD na integração contínua
O desenvolvedor precisa rodar testes rápidos localmente; o servidor de integração confirma a mudança em ambiente limpo.
Organize etapas: lint e unidades primeiro, integração depois, verificações mais caras por último.
Falhas rápidas economizam tempo e evitam que um erro simples ocupe toda a esteira.
Bloqueie a integração quando testes obrigatórios falharem e trate instabilidade como defeito. Uma suíte ignorada perde o papel de rede de segurança.
O artigo sobre automação de deploy com CI/CD ajuda a conectar essas verificações à entrega.
Como adotar TDD em equipe
- Comece por uma regra pequena e frequente, não pelo módulo mais acoplado.
- Faça sessões de pareamento para tornar decisões visíveis.
- Defina convenções de nomes, estrutura e doubles.
- Mantenha a suíte de unidade rápida e determinística.
- Revise testes e código de produção juntos.
- Registre exemplos de defeitos antes de corrigi-los.
A adoção parcial pode gerar estilos incompatíveis e manutenção desigual. Um acordo simples — toda nova regra crítica começa com um exemplo falhando — é mais sustentável que exigir TDD em qualquer linha.
Avalie resultados em retrospectivas e ajuste o escopo.
Quando TDD costuma funcionar melhor
TDD tende a entregar mais valor em regras de negócio, transformações de dados, algoritmos, validações, parsers, cálculos, APIs e correções de defeitos. Nesses casos, entradas e saídas são claras e o comportamento pode ser dividido em exemplos pequenos.
Ele também ajuda quando o código mudará por anos e o custo de regressão é alto.
Frameworks diferentes suportam o mesmo ciclo. Além de Jest e JUnit, o pytest mostra como criar testes simples em Python.
A ferramenta muda; o hábito de obter uma falha significativa antes da implementação permanece.
Quando não vale forçar TDD
Explorações descartáveis, protótipos visuais e investigação de uma tecnologia desconhecida podem começar melhor com um spike. Aprenda, descarte ou estabilize a descoberta e então escreva testes para o comportamento que seguirá adiante.
Não transforme um experimento em produção sem proteção.
Também é improdutivo testar detalhes gerados, bibliotecas de terceiros ou estilos visuais por unidade.
Nesses casos, testes de integração, contrato, snapshot criterioso ou validação visual podem ser mais adequados.
A decisão deve considerar risco, custo e qualidade do feedback.
Erros comuns e testes frágeis
- Escrever vários testes antes de executar o primeiro red.
- Não confirmar por que o teste falhou.
- Implementar funcionalidades além do cenário atual.
- Pular refatoração indefinidamente.
- Usar sleeps, relógio real ou serviços externos.
- Mockar toda chamada interna.
- Compartilhar estado mutável entre testes.
- Testar texto de erro ou ordem sem relevância contratual.
- Manter testes lentos ou instáveis na suíte rápida.
Quando um teste quebra após uma refatoração que preservou o comportamento, examine seu nível de acoplamento.
Quando nunca quebra, verifique a asserção e provoque uma mutação intencional. A suíte deve ser sensível a defeitos relevantes e tolerante a mudanças internas.
Um plano de prática para começar
Na primeira semana, pratique katas de 20 a 30 minutos com funções puras. Na segunda, aplique a técnica a uma regra real e pequena. Na terceira, introduza uma dependência por interface e experimente um fake. Na quarta, corrija um defeito começando pelo teste que o reproduz.
Registre o tempo entre red e green, não para competir, mas para perceber ciclos grandes.
Se uma etapa dura horas, decomponha melhor. Leia falhas em voz alta, mantenha uma lista de próximos exemplos e pare quando o comportamento atual estiver completo.
Checklist para cada ciclo de TDD
- O teste descreve um comportamento relevante?
- Ele falhou antes da implementação?
- A mensagem de falha mostrou o motivo esperado?
- O green exigiu uma mudança pequena?
- A suíte relacionada continua verde?
- Há duplicação, nome ruim ou responsabilidade confusa?
- A refatoração preservou o comportamento?
- O próximo exemplo é realmente necessário?
Perguntas frequentes sobre TDD
TDD deixa o desenvolvimento mais lento?
No início, escrever e organizar testes adiciona esforço visível. O benefício aparece ao reduzir depuração, regressões e medo de mudar.
Em um protótipo descartável, o retorno pode ser baixo; em software mantido por anos, feedback rápido tende a compensar. Meça o fluxo completo, não apenas o tempo para digitar a primeira versão.
É preciso escrever todos os testes primeiro?
Não. TDD trabalha com um teste pequeno por vez. Você pode manter uma lista de exemplos futuros, mas implementa, refatora e aprende a cada ciclo.
Escrever uma bateria completa antecipadamente recria uma grande especificação e reduz a capacidade de ajustar o design.
TDD serve apenas para backend?
Não. Funções de interface, reducers, validações, acessibilidade e componentes podem ser guiados por comportamento. Contudo, detalhes visuais pedem ferramentas complementares.
O princípio é escolher a menor fronteira observável e um teste que dê feedback confiável.
A meta deve ser 100% de cobertura?
Não como regra universal. Cobertura total pode ser justificável em um núcleo crítico e puro, mas custa caro em integrações e código trivial. Priorize risco e comportamento.
A meta saudável é uma suíte capaz de detectar regressões importantes com velocidade e clareza.
Conclusão
Test-Driven Development transforma testes em instrumento de design e feedback. O valor está na cadência: falha significativa, implementação mínima e refatoração segura.
Passos pequenos expõem requisitos, limitam o trabalho em andamento e deixam uma documentação executável do comportamento.
Comece por uma regra curta, confirme o red e resista à vontade de antecipar abstrações. Depois, leve a mesma disciplina para correções, legado e integração contínua.
TDD não elimina todas as incertezas, mas torna cada decisão menor, observável e mais fácil de corrigir.