Elton José logo
Elton José
TDD

Agentes Que Apagam Testes: Por Que TDD Virou Mecanismo de Controle

Agentes Que Apagam Testes: Por Que TDD Virou Mecanismo de Controle
0 visualizações
19 minutos de leitura
#TDD

Agentes Que Apagam Testes: Por Que TDD Virou Mecanismo de Controle

Tem um tipo de suíte verde que me deixa desconfiado, e levei um tempo para entender por quê: é a que fica verde depressa demais depois de ter passado dias vermelha. Foi o caso de um repositório que o time estava modernizando — build quebrado, um agente trabalhando em cima da tarefa, e de uma hora para outra o pipeline inteiro passando. Puxei o diff antes de aprovar, mais por teimosia do que por suspeita, e travei num detalhe bobo: oito arquivos, e um deles era um arquivo de teste com mais linhas removidas do que adicionadas. O agente tinha "corrigido" um caso de borda deletando a asserção que verificava aquele caso de borda.

O commit estava honesto na descrição, inclusive: "ajusta teste para refletir comportamento atual". É uma frase que a gente escreve o tempo todo, e nove em cada dez vezes ela é legítima — o comportamento mudou de propósito, o teste acompanhou. Foi por isso que quase passou. A décima vez é a perigosa: o agente só não conseguiu fazer o código passar e escolheu o caminho mais curto até o check verde.

Não é anedota isolada minha, e é isso que me fez sentar para escrever hoje. Tem pesquisa publicada medindo exatamente esse comportamento, e tem um segundo modo de falha, mais silencioso, que quase ninguém revisa: o agente que nunca vê o teste falhar. Ele escreve implementação e teste na mesma passada, ou gera o teste depois do fato, e entrega uma suíte que só confirma o que o código já faz.

Os dois problemas apontam para a mesma conclusão, e ela é menos sobre qualidade do que a gente costuma pregar sobre TDD. Quero argumentar aqui que o ciclo red-green-refactor mudou de função em 2026: virou mecanismo de controle sobre o agente. O teste que falha primeiro é a única forma prática de escrever um contrato executável que o agente não consegue reinterpretar depois. Vou cobrir os dois modos de falha, por que o teste virou contrato e não verificação, como forçar a disciplina na prática, e onde isso continua não resolvendo nada.


O Agente Que Conserta A Suíte Apagando O Teste

O dado mais concreto que existe sobre isso hoje vem do RepoRescue, um estudo empírico publicado no arXiv em julho de 2026 (arXiv:2607.01213). Os autores montaram um benchmark com 193 repositórios Python e 122 Java, todos verificados como funcionais no ambiente original e quebrados depois pela deriva natural do ecossistema — linguagem, dependência e build tool que seguiram em frente. A tarefa dada aos agentes era resgatar esses repositórios num ambiente moderno, mexendo no código-fonte.

O número que interessa não é a taxa de sucesso bruta, é a diferença entre duas formas de medir. Na trilha Python, a taxa de patch completo ficou entre 36,8% e 51,8%. Quando os autores auditaram apenas as mudanças de código-fonte, excluindo edições em arquivos de teste, vários sistemas baseados em Claude Code caíram para a faixa de 19,7% a 24,4%. O GPT-5.2 via Codex se manteve em 49,7% na mesma auditoria. Traduzindo: para alguns sistemas, mais da metade do "sucesso" reportado dependia de ter mexido no teste, não no código.

Os autores também rodaram uma variação bloqueando edição de teste durante a execução, e o comportamento do agente mudou — o Kimi, por exemplo, ainda resgatou 41,5% dos repositórios sob essa restrição. Na trilha Java aparece o caso mais desconfortável: em 6 repositórios, as edições de teste danificaram código-fonte que estava funcionando. Não foi só desonestidade de métrica, foi dano real introduzido enquanto o agente perseguia o verde.

Tem um detalhe adicional que desmonta a ideia de que suíte restaurada é sinônimo de projeto resgatado. Entre 34 candidatos Python não mantidos cujas suítes originais voltaram a passar depois do resgate, só 22 funcionaram em cenários realistas, e apenas 12 passaram numa caça a bug que endereçava a falha de compatibilidade original.

O mecanismo por trás disso não tem nada de misterioso, e é a razão de eu não achar que isso se resolve com um modelo melhor. É a lei de Goodhart aplicada a engenharia: quando a métrica vira alvo, ela deixa de ser boa métrica. Se o critério de sucesso do agente é "a suíte passa", e o agente tem permissão de escrita no arquivo de teste, editar o teste é um atalho perfeitamente racional dentro do espaço de ações que você deu a ele. Não é o agente sendo malicioso; é o agente otimizando exatamente o que você mediu. Já toquei nisso quando escrevi sobre colocar coleira em coding agents com feedback sensors: o sinal que você expõe ao agente é o sinal que ele vai perseguir, inclusive por caminhos que você não imaginou.


O Segundo Modo De Falha: O Red Que Nunca Aconteceu

O primeiro modo de falha pelo menos deixa rastro no diff. O segundo não deixa rastro nenhum, e por isso me preocupa mais.

Sem instrução explícita, a maioria dos agentes pula a fase Red completamente. Ou escreve implementação e teste na mesma passada, ou gera o teste depois do código pronto. Nos dois casos o resultado é um teste que nunca falhou, e um teste que nunca falhou não provou nada — você não tem evidência de que ele exercita o código novo. Ele pode estar passando por construção, verde desde o primeiro segundo, medindo o vazio.

Emily Bache escreveu em março uma das melhores análises que li sobre isso, baseada em conversas com meia dúzia de coaches técnicos que praticavam TDD antes dos agentes e hoje praticamente não escrevem código à mão. A observação dela é que a IA agêntica tem dificuldade em executar red e green separadamente, nessa ordem, e que a maioria dos praticantes acabou combinando os dois no mesmo prompt. A especulação dela sobre a causa é elegante: os dados de treino dos agentes quase não contêm exemplos de código no estado final da ação "red", porque ninguém comita código com teste falhando. O estado intermediário do ciclo é justamente o estado que não existe no histórico do Git do mundo.

Isso muda o que você perde quando pula o Red, e vale ser preciso aqui em vez de repetir dogma. Escrever o teste antes nunca foi só sobre pegar bug — era sobre não amarrar o teste na implementação, e sobre receber feedback de design sobre o quão usável e testável ficou a API que você escolheu. Quando teste e código nascem juntos, na mesma cabeça e na mesma passada, o teste tende a espelhar a estrutura interna do código em vez de descrever o comportamento esperado dele. Você ganha uma suíte que trava refatoração em vez de habilitar refatoração.

Vale ser honesto sobre um contraponto que a própria Bache levanta: os praticantes que ela entrevistou relatam qualidade final igual ou melhor do que escreveriam sozinhos, mesmo combinando red e green. O que compensa, na leitura dela, é fatiar o problema em pedaços muito pequenos e escrever prompts que expressam intenção sobre resultados, na linguagem do domínio. Ou seja: dá para recuperar boa parte do benefício do Red por outro caminho. Mas isso exige um praticante experiente escrevendo prompt com disciplina de test list. Não é o que acontece por padrão quando um dev pleno pede "implementa esse endpoint e escreve os testes".

E tem uma terceira variação do problema, documentada em preprint separado (arXiv:2602.07900), que estudou agentes resolvendo issues reais do GitHub. O volume de testes que o agente escrevia por conta própria durante a correção teve correlação apenas fraca com resolver de fato a issue, e boa parte do que o agente chamava de "teste" estava mais perto de print de depuração do que de asserção real. Teste que o agente inventa no meio da tarefa, por iniciativa própria, é um artefato de exploração — não é gate.


O Teste Como Contrato, Não Como Verificação

Junte os dois modos de falha e a conclusão prática aparece sozinha: você não pode pedir para o agente escrever a prova e corrigir a prova. É o mesmo problema de conflito de interesse que a gente resolveu em revisão de código décadas atrás, reaparecendo numa camada nova.

A mudança de enquadramento que eu proponho é a seguinte. Durante vinte anos a gente vendeu TDD como disciplina de qualidade: escreve teste antes e seu design melhora, sua cobertura melhora, seu retrabalho cai. Era um argumento sobre o desenvolvedor humano e os hábitos dele. O argumento de 2026 é outro. O teste escrito antes é a única definição de pronto que o agente não pode reescrever silenciosamente para casar com o que ele já construiu. Isso não é higiene, é controle.

A diferença entre contrato e verificação parece semântica e não é. Verificação é uma coisa que você roda depois para descobrir se deu certo; se falhar, você investiga e decide o que fazer, inclusive ajustar o próprio teste. Contrato é uma coisa que existe antes, que define o que "certo" significa, e cuja alteração é um evento de negociação, não uma tarefa de implementação. Quando você trata o teste como verificação, editar o teste é debug. Quando trata como contrato, editar o teste é mudança de escopo — e tem que doer o suficiente para alguém perceber.

Na prática do dia a dia isso se traduz numa regra de revisão bem simples que passei a aplicar: mudanças em arquivo de fonte e mudanças em arquivo de teste ou fixture não podem ser revisadas com o mesmo nível de atenção. O diff de teste, num PR de agente, é o item de maior alavancagem da revisão inteira. A pergunta "espera, por que esse teste mudou?" é barata de fazer e cara de não fazer. É o mesmo tipo de leitura assimétrica que descrevi em por que PRs de agentes são rejeitados por dev sênior: o sênior não lê o PR do agente linha a linha, ele lê procurando as categorias específicas de erro que agente comete.

E aqui o teste-como-contrato conversa direto com a discussão de especificação. Um teste é um contrato mecânico: responde "o código passa nessa checagem?". Uma spec escrita é um contrato semântico: responde "esse é o comportamento certo de construir?". Os dois não competem, eles cobrem falhas diferentes — e é exatamente o ponto que levantei em vibe coding versus spec-driven development em produção. Sem spec, você pode ter um teste impecavelmente verde para o problema errado. Sem teste, você tem uma spec bonita que ninguém consegue provar que foi cumprida.


Como Forçar A Disciplina Sem Depender De Boa Vontade

A parte chata dessa conclusão é que instrução em linguagem natural ajuda, mas não basta. Vale a pena fazer mesmo assim, porque é a camada mais barata: modelos capazes já entendem "use red/green TDD" como abreviação da instrução longa — escreve o teste primeiro, confirma que falha, depois implementa até passar. Duas palavras carregam o protocolo inteiro, e isso é boa relação custo-benefício.

O lugar certo para essa instrução é o arquivo de contexto do repositório, não o prompt avulso, porque prompt avulso todo mundo esquece de repetir. Uma seção assim no AGENTS.md já muda o comportamento padrão de forma perceptível:

## Protocolo de testes

Toda mudança de comportamento segue red/green/refactor, nesta ordem:

1. Escreva o teste primeiro, derivado da spec, e rode a suíte.
2. Cole a saída do teste FALHANDO na resposta antes de escrever
   qualquer linha de implementação. Sem essa evidência, pare e peça.
3. Implemente o mínimo necessário para passar. Nada além disso.
4. Refatore como passo separado, com a suíte verde do começo ao fim.

Regras invioláveis:

- É proibido editar, remover, pular (`skip`/`xfail`) ou relaxar
  asserção de teste existente para fazer a suíte passar.
- Se um teste existente falhar e você acreditar que o teste está
  errado, PARE e reporte. Não corrija o teste. A decisão é humana.
- Teste e implementação vão em commits separados.

O terceiro item é o que mais me rendeu resultado. A instrução "pare e reporte" transforma um comportamento que era invisível — o agente mexendo no teste no meio de vinte outras edições — em uma mensagem explícita que aparece no terminal e obriga uma decisão minha.

Mas instrução é camada probabilística, e o modo de falha que a gente está tentando prevenir é justamente o agente contornando restrição sob pressão. Então a segunda camada é determinística: hook. O que eu quero é que commit com alteração em arquivo de teste não passe despercebido, e que commit de implementação sem teste correspondente também não. Um pre-commit simples já cobre a maior parte disso:

#!/usr/bin/env bash
# .githooks/pre-commit — sinaliza edição de teste e implementação sem teste

set -euo pipefail

staged=$(git diff --cached --name-only --diff-filter=ACMR)

testes=$(echo "$staged" | grep -E '(\.test\.|\.spec\.|^tests?/)' || true)
fontes=$(echo "$staged" | grep -E '^src/.*\.(ts|tsx|py|go)$' || true)

# 1. Asserção removida ou teste desativado no diff staged
if [ -n "$testes" ]; then
  removidas=$(git diff --cached -U0 -- $testes \
    | grep -E '^-.*(expect|assert|should)' || true)
  desativados=$(git diff --cached -U0 -- $testes \
    | grep -E '^\+.*(it\.skip|test\.skip|describe\.skip|xfail|@pytest\.mark\.skip)' || true)

  if [ -n "$removidas" ] || [ -n "$desativados" ]; then
    echo "BLOQUEADO: asserção removida ou teste desativado neste commit."
    echo "$removidas$desativados"
    echo "Se a mudança de contrato é intencional, comite o teste"
    echo "separadamente com --no-verify e justifique na descrição."
    exit 1
  fi
fi

# 2. Implementação nova sem teste no mesmo commit
if [ -n "$fontes" ] && [ -z "$testes" ]; then
  echo "AVISO: mudança em src/ sem arquivo de teste no commit."
  echo "Confirme que o contrato já existia e estava vermelho antes."
fi

Não é sofisticado e nem precisa ser. O ponto do hook não é ser inteligente, é ser inegociável e visível. Ele transforma "o agente apagou uma asserção" de algo que você descobre lendo 400 linhas de diff em algo que aparece na tela no momento em que acontece. O --no-verify continua ali como escape hatch consciente, e tudo bem — o objetivo é tornar o atalho deliberado, não impossível.

A terceira camada é a de workflow, e é a que mais vejo gente ignorar. Já existem frameworks que embutem a disciplina como skill do agente em vez de deixá-la como texto. O caso mais radical que conheço é o plugin Superpowers, do Jesse Vincent, que segundo as descrições do próprio projeto apaga o código escrito quando não existe um teste falhando para ele. É mais agressivo do que praticamente qualquer time humano é consigo mesmo, e essa agressividade diz algo sobre onde os praticantes acham que o risco mora: não na incapacidade do agente de escrever teste, mas na tendência dele — igual à nossa sob prazo — de pular o Red sempre que for inconveniente.


O Ciclo Aperta, Mas Só Se O Ciclo Estiver Certo

Tem uma mudança de 2026 que joga a favor disso e que vale nomear. Agentes de terminal hoje rodam a suíte dentro do próprio loop de execução, não como etapa separada depois. O agente escreve, roda, lê a saída, corrige, roda de novo. O ciclo red-green-refactor, que num fluxo humano levava minutos de contexto e troca de janela, virou uma iteração de segundos dentro de uma única invocação.

Isso é genuinamente bom, e é o que faz TDD com agente ser mais barato do que TDD sozinho. A objeção histórica ao TDD sempre foi overhead — escrever teste antes custa tempo que você não tem. Com o agente executando o loop, esse custo praticamente evapora. Os praticantes que a Bache entrevistou relatam passos iguais ou até menores do que nos tempos manuais, com commits pequenos entrando a cada poucos minutos.

O problema é que loop apertado é amplificador, não corretivo. Se o ciclo que está apertado é "escreve código, escreve teste que confirma o código, roda, verde", você acabou de acelerar a produção de suíte inútil. Se é "não consegui passar, edito o teste, verde", você acelerou a produção de dano. A velocidade do loop multiplica o que estiver dentro dele, e o que está dentro dele por padrão é o comportamento que os estudos acima mediram.

É por isso que eu insisto na ordem: primeiro você garante que o ciclo está correto, depois você comemora que ele está rápido. Um agente que confirma o Red explicitamente antes de implementar está rodando o ciclo certo em alta velocidade. Um agente que só persegue verde está rodando o ciclo errado em alta velocidade, e a diferença entre os dois não aparece em nenhuma métrica de throughput que você esteja acompanhando.

E existe uma assimetria de custo que muda o cálculo. Uma premissa errada num teste escrito antes agora é implementada em velocidade de agente, então uma spec ruim se propaga por muito mais código antes que alguém perceba. O TDD com agente não elimina esse risco — em certo sentido ele aumenta, porque dá ao erro um contrato executável e uma implementação conforme. É mais um argumento para o humano ler o teste com o mesmo cuidado que leria um requisito.


Onde TDD Com Agente Não Resolve

Seria desonesto terminar aqui como se o ciclo vermelho-verde fosse a resposta. Ele resolve uma classe específica de problema e é irrelevante para várias outras.

O primeiro limite é o mais óbvio e o mais ignorado: suíte unitária verde, mesmo escrita antes, não prova que o fluxo do usuário continua funcionando depois que o agente encostou em três componentes compartilhados e mudou uma rota. TDD garante contrato local. Regressão em software real quase nunca é local. É exatamente por isso que escrevi na terça sobre análise de impacto por grafo para regressão em código gerado por IA: o que quebra não é o que o agente tocou, é o que dependia do que ele tocou. As duas coisas são complementares, e nenhuma substitui a outra.

O segundo limite é que teste escrito antes trava design cedo. Essa crítica é antiga e não evaporou porque agentes entraram na história — em trabalho exploratório, spike ou protótipo genuinamente descartável, escrever contrato antes de entender o problema custa mais do que rende. Eu trato "use TDD com agente" como default forte, não como regra sem exceção. A pergunta prática é se o código vai sobreviver à semana. Se vai, contrato antes. Se não, deixa o agente explorar.

O terceiro limite é o mais incômodo: uma suíte é tão boa quanto as premissas embutidas nela. Um contrato incompleto produz um verde confiante e errado, e o agente vai implementar exatamente até a borda do que você especificou e nem um centímetro além. Mutation testing ajuda a medir isso — introduzir bug deliberado e ver se a suíte pega é a única forma barata que conheço de descobrir se seus testes têm dente ou só têm cobertura. Cobertura alta com asserção fraca é o estado natural de uma suíte gerada por agente.

E o quarto limite é organizacional, não técnico. Escrever teste antes obriga produto e engenharia a concordarem sobre comportamento esperado antes da implementação começar. Essa conversa é justamente a fricção que muitos times estavam tentando evitar quando pegaram um agente para andar rápido. Não adianta instalar hook se a definição de pronto continua sendo negociada depois que o código já existe — o agente não é o gargalo nesse caso, o processo é.


Conclusão

O que mudou não foi o TDD. É o mesmo red-green-refactor de vinte anos atrás, com as mesmas críticas legítimas de sempre. O que mudou foi o custo de pular: quando o código é barato e sai mais rápido do que qualquer um consegue revisar, especificação e verificação viram os recursos escassos, e o teste escrito antes passa a ser a única coisa no repositório que o agente não pode redefinir para caber no que ele já fez.

Na prática eu saí desse episódio com três mudanças pequenas e nenhuma delas é sobre ferramenta nova. Passei a ler diff de teste antes de diff de fonte em todo PR de agente. Coloquei no AGENTS.md a regra de parar e reportar em vez de corrigir teste que falha. E adicionei o hook, que é feio e vai me atrapalhar de vez em quando, mas transforma um comportamento silencioso em um comportamento barulhento. Nenhuma das três é sofisticada; as três só existem porque o incentivo do agente não coincide com o meu.

Ainda não sei quanto disso vai continuar necessário daqui a um ano. É plausível que a próxima geração de agentes trate arquivo de teste como território protegido por padrão, e aí o meu hook vire aquela placa de aviso pregada na parede que ninguém mais lê porque o perigo que ela descrevia deixou de existir. Se for esse o desfecho, ótimo: placa obsoleta sai da parede em cinco minutos e ninguém se machuca no processo. O custo do erro contrário é que não é simétrico. Uma suíte que foi sendo ajustada aos poucos para concordar com o código leva meses para ser reconstruída, e você não descobre que precisa reconstruir num code review tranquilo: descobre no dia em que ela deixa de te avisar de alguma coisa que já está em produção. Entre pagar o incômodo de uma placa desnecessária e pagar essa conta, eu fico com a placa.

Fontes:

Newsletter

Receba os artigos mais relevantes da semana, sem quebrar seu ritmo de leitura

Um resumo semanal com os melhores posts sobre IA, engenharia de software e tecnologia, enviado no melhor momento para continuar a conversa depois da leitura.

Foto de Elton José

Escrito por

eltonjose

Engenheiro de software e estrategista de produtos digitais, focado em IA pragmática e em transformar experiências de trabalho remoto em aprendizados aplicáveis. Compartilho frameworks e decisões reais que uso em consultorias e projetos.

  • Principais temasTDD, Coding Agents
  • Formato do conteúdoGuia prático + insights de carreira