O test driven development estrito quase nunca sobrevive a um deadline real

O artigo analisa por que o TDD estrito de Kent Beck raramente sobrevive a deadlines reais, código legado e pressão de entrega, e defende uma versão pragmática: test-first para lógica de negócio e casos extremos, não para código trivial de UI ou encanamento.

Uma equipe de quatro pessoas revisa código junta em frente a uma tela no escritório, um deles apontando algo no monitor, foto em preto e branco.
15 de jul. de 20269 min de leitura
Atualizado em 4 de ago. de 2026

Você com certeza já esteve nessa reunião. Alguém da equipe propõe fazer test driven development como deveria ser neste sprint: escrever o teste que falha, escrever o código mínimo para que passe, refatorar, repetir. Vermelho, verde, refatorar. Todo mundo concorda. Duas semanas depois o diagrama do quadro branco sumiu e ninguém mais menciona aquilo.

Esse ciclo não é um mito. O livro de Kent Beck descreve uma prática específica e disciplinada: você escreve um teste antes de existir uma única linha de código de produção, vê ele falhar pelo motivo certo, escreve a quantidade mínima de código que o faz passar, e só depois limpa o design. Feito exatamente como está escrito, é tanto uma técnica de design quanto uma técnica de testes. Os testes são um efeito colateral. O produto real é um código que vai se formando de maneira incremental a partir de um feedback constante e ajustado.

O dogma vira problema quando esse ciclo é tratado como uma lei universal em vez de uma ferramenta com um trabalho específico. O test driven development estrito assume que você está partindo de um arquivo vazio, que os requisitos são estáveis o suficiente para serem codificados numa asserção, e que você tem tempo para deixar o design emergir barra vermelha após barra vermelha. A maioria dos engenheiros sêniores passa muito pouco da carreira nessa situação.

Onde o test driven development estrito quebra em equipes de produção reais

Três coisas matam a versão de manual no contato com um trabalho real.

A primeira é o código legado. Você não começa de um arquivo vazio. Te entregam uma classe de serviço de 4.000 linhas sem nenhuma costura, escrita por alguém que saiu da empresa há três anos, e um ticket que diz "corrigir o null pointer no checkout". Michael Feathers escreveu um livro inteiro sobre esse problema porque o livro do Beck não aborda isso: você não consegue escrever um teste primeiro para um código que já existe e que não tem injeção de dependências, nem interfaces, nem efeitos colaterais separados da chamada ao banco de dados. O primeiro passo real aqui é escrever testes de caracterização, testes que fixam o que o código faz atualmente, não o que deveria fazer. Isso é test driven em espírito, mas não é o ciclo vermelho-verde-refatorar. Parece mais engenharia reversa com uma rede de segurança.

A segunda é a pressão de entrega. O TDD estrito pede que você escreva um teste para uma feature cujo formato pode mudar duas vezes antes do almoço. Quando o time de produto ainda está decidindo se um campo é obrigatório ou opcional, escrever uma suíte de testes exaustiva desde o primeiro momento é uma aposta que você vai perder. Os engenheiros sêniores que insistem na disciplina completa de test-first nessa situação costumam acabar jogando fora metade dos testes quando os requisitos mudam, que é exatamente o tipo de desperdício que o TDD deveria evitar.

A terceira é o volume enorme de código que não tem nenhuma lógica relevante. Um getter. Um mapeador de DTO. Um componente React que recebe uma prop user e renderiza um nome e um avatar. Escrever primeiro um teste que falha para <UserBadge user={user} /> antes de escrever o componente não te dá nada em troca. Não existe uma ramificação, nem um cálculo, nem uma transição de estado que você possa estragar. Você está testando que o JSX renderiza o que você disse para renderizar. Isso não é rigor, é cerimônia.

O subconjunto de test driven development que sobrevive de verdade

Aqui está a parte de que não se fala o suficiente: os engenheiros que entregam sistemas confiáveis sob deadlines reais não são os que abandonaram o TDD. São os que o delimitaram.

O que sobrevive é escrever o teste primeiro para tudo que tem lógica de decisão real: ramificações, cálculos, máquinas de estado, qualquer coisa em que um output errado é difícil de notar a olho nu e caro de detectar tarde demais. O que fica de lado é o test-first para o código estrutural: fiação, cola, componentes de passagem, objetos de configuração. A versão sênior do TDD não é "testar tudo antes de escrever". É "saber em quais dez por cento do seu código os bugs realmente se escondem, e escrever esses dez por cento com o teste primeiro".

Isso não é uma versão diluída da ideia do Beck. Se parece mais com o que as equipes experientes já vinham fazendo antes de "TDD" virar uma palavra da moda boa para certificação: escrever o teste primeiro quando o código é genuinamente difícil de acertar na primeira tentativa, e escrevê-lo depois (ou simplesmente não escrevê-lo, para os casos verdadeiramente triviais) quando não é.

Uma linha concreta: lógica de preços versus um pass-through de props no React

Um exemplo concreto, porque "lógica de negócio" é algo vago até você ver ao lado de algo que não é.

Suponhamos que você está construindo uma calculadora de preços para um produto de assinatura: preço base, uma regra de rateio para upgrades no meio do ciclo, um multiplicador de impostos regional, e uma pilha de descontos que precisa ser aplicada numa ordem específica porque é assim que o time financeiro pede. Escreva o teste primeiro aqui. Escreva it("aplica o desconto percentual antes do desconto fixo quando os dois estão presentes") antes de escrever uma única linha do cálculo. Você vai errar a ordem dos descontos na primeira tentativa. Acontece com todo mundo. O teste detecta isso em três segundos em vez de um ticket de suporte de um cliente que foi cobrado errado quatro meses depois. Esse é exatamente o caso para o qual o livro do Beck foi escrito: uma regra complexa o suficiente para que sustentá-la inteira na sua cabeça enquanto você escreve código seja uma aposta perdida.

Agora pegue o componente <UserBadge user={user} /> de antes. Ele recebe um objeto user e renderiza um nome, um avatar e um indicador de status online. Não existe nenhuma regra que você possa estragar. Um snapshot test ou uma revisão visual rápida depois do fato te diz tudo o que um ciclo test-first te diria, com uma fração da cerimônia. Forçar um ciclo vermelho-verde-refatorar nesse componente não torna o código mais seguro. Torna o pull request mais longo e faz os olhos de quem revisa embaçarem.

A linha não é "backend versus frontend" nem "difícil versus fácil de escrever". É "se errar nisso custa algo que não se vê a olho nu". Um cálculo de preços, os casos extremos de um rate limiter, uma máquina de estado para o ciclo de vida de um pedido, um cálculo de datas que atravessa fusos horários: todos esses merecem uma abordagem test-first. Um componente que só repassa props para um markup, não.

Como isso se parece semana a semana

Na prática, isso significa que a sua suíte de testes está desbalanceada de propósito. O módulo de preços, a lógica de renovação do token de auth, o código de retentativas com backoff, o parser de CSV que precisa lidar com os formatos ligeiramente diferentes de três fornecedores: esses têm testes escritos antes ou junto com a implementação, e são específicos sobre os casos extremos, não só sobre o caminho feliz. O controller que chama três serviços e devolve o resultado combinado, o config loader, a biblioteca de componentes: esses também têm testes, mas escritos depois, principalmente para fixar o comportamento quando ele já está estável, e a ninguém tira o sono a ordem.

Isso também muda como você revisa código. Um pull request que adiciona uma regra de desconto sem nenhum teste anexado é um achado real, que vale a pena bloquear. Um pull request que adiciona uma prop nova a um componente de visualização sem teste anexado não é algo que você deveria estar comentando, e se o processo da sua equipe trata os dois casos da mesma forma, esse processo está otimizando para uma métrica (o percentual de cobertura) em vez do que a cobertura deveria proteger (a confiança no código que realmente é arriscado).

A disciplina que importa não é "escreva os testes primeiro, sempre". É saber, antes de escrever uma única linha, se o que você está prestes a construir é do tipo de coisa em que você pode errar sem perceber. Se for, pare e escreva o teste primeiro. Se não for, escreva o código e siga em frente. Esse critério é a habilidade real. O ritual do vermelho-verde-refatorar é só uma forma de exercitá-lo.

Onde fica o argumento

O test driven development, como prática estrita, não é algo que a maioria das equipes de produção faz, e não é algo que a maioria das equipes de produção deveria fazer em todo o código. Isso não é um fracasso da ideia. É um descompasso entre uma técnica desenhada para um tipo de problema (código cuja correção é difícil de verificar a olho nu) e a realidade de que a maior parte de um código não é esse tipo de problema. Aplicá-la de forma uniforme é a razão pela qual as equipes a abandonam duas semanas depois da sessão no quadro branco: estavam pagando o custo completo da disciplina em código que nunca precisou dela.

A versão que sobrevive ao contato com um deadline real é mais delimitada e, honestamente, mais útil do que a do livro: test-first para os cálculos, as transições de estado e os casos extremos que tiram o seu sono, e critério de engenharia normal para todo o resto. Isso não é TDD levinho. É TDD apontado para a parte do trabalho onde realmente vale a pena.

ESCRITO POR

Logotipo de Howdy.com
Redacción Howdy.com
COMPARTILHAR