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

Sumário
- Agentes Que Apagam Testes: Por Que TDD Virou Mecanismo de Controle
- O Agente Que Conserta A Suíte Apagando O Teste
- O Segundo Modo De Falha: O Red Que Nunca Aconteceu
- O Teste Como Contrato, Não Como Verificação
- Como Forçar A Disciplina Sem Depender De Boa Vontade
- Protocolo de testes
- .githooks/pre-commit — sinaliza edição de teste e implementação sem teste
- 1. Asserção removida ou teste desativado no diff staged
- 2. Implementação nova sem teste no mesmo commit
- O Ciclo Aperta, Mas Só Se O Ciclo Estiver Certo
- Onde TDD Com Agente Não Resolve
- Conclusão
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."
fiNã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:
- DevAssure — Why Test-Driven Development Is Having a Second Act in the Age of AI Coding Agents
- DevAssure — Your AI Coding Agent Might Be Gaming Its Own Tests
- arXiv — RepoRescue: An Empirical Study of LLM Agents on Whole-Repository Compatibility Rescue (2607.01213)
- Emily Bache — Test-Driven Development with Agentic AI
- QASkills.sh — TDD with AI Agents: Best Practices for 2026
- Simon Willison — Red/green TDD (Agentic Engineering Patterns)
- arXiv — Rethinking the Value of Agent-Generated Tests for LLM-Based Software Engineering Agents (2602.07900)
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.

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
