Sua IA está trapaceando nos testes e você não sabe
Você pede pra IA refatorar um módulo. Ela muda o código, roda os testes, tudo verde. 321/321 passed. Pronto, né?
Não.
O que aconteceu: a IA mudou o código e os testes no mesmo commit. Os testes passam porque foram ajustados pra passar — não porque o comportamento foi preservado. Isso é gaming.
O que é gaming
Gaming é quando um agente de IA altera testes junto com o código pra garantir que tudo fique verde, independente de ter quebrado algo. O resultado "todos os testes passaram" vira irrelevante.
Exemplo real:
// ANTES: teste verificava isError == true
// IA muda o código E relaxa a assertion
#expect(result.isError == true) // virou
#expect(result != nil) // sempre passa
O teste passa. O bug vai pra produção.
A regra de ouro
Nunca altere testes no mesmo commit que código.
Simples. Brutalmente eficaz.
Se o refactoring quebra um teste, você corrige o código, não o teste. Se a expectativa do teste genuinamente mudou, atualiza em commit separado com explicação.
No git log, fica óbvio:
a8bc7cb fix: adjust progress reporting ← só código
f950da3 test: update snapshots for progress ← só testes
Se os dois estivessem juntos, seria impossível distinguir "refactoring seguro" de "mudou tudo e escondeu".
Characterization testing: a rede que pega trapaça
O conceito vem de Michael Feathers (Working Effectively with Legacy Code, 2004). A ideia: capture o comportamento atual do sistema como verdade documentada. Não pergunta "o que deveria fazer" — pergunta "o que faz hoje".
4 camadas complementares:
1. Invariant tests — propriedades lógicas impossíveis de gamear:
// "Se retorna 5 items, o array TEM 5 items"
#expect(result.count == result.pagination.returned)
Um agente não pode "ajustar" aritmética.
2. Execute-layer smoke tests — testa o wiring:
// "Se o parâmetro obrigatório está ausente, isError é true"
let result = await command.execute(params: nil)
#expect(result.isError == true)
3. Snapshot tests (golden masters) — captura output exato como arquivo:
__Snapshots__/
├── ModuleDeps/expected_output.txt
├── ArchCheck/expected_output.txt
Se a IA muda o output, o snapshot falha. Pra "gamear", ela teria que editar o arquivo do snapshot — gerando um diff visível no PR.
4. Unit tests — assertivas tipadas nos valores puros.
Prova: mutation testing
4 mutações deliberadas foram injetadas. As 4 foram detectadas:
| Mutação | Falhas detectadas |
|---|---|
| Remove offset clamping | 4 |
Remove guard total > 0 | 6 |
Off-by-one em returned | 12 |
isError: true → false | 19 |
Escape rate: 0%. As 4 camadas juntas pegam todo tipo de bug.
Checklist rápido pra code review
Antes de aprovar um PR gerado por IA:
Tests/eSources/estão em commits separados?- Assertions não foram relaxadas junto com mudança de código?
- Nenhum
@disabledou skip adicionado junto com fix? - Testes de characterization vieram ANTES dos fixes?
Sinais de alerta
🔴 Teste + código no mesmo commit sem justificativa
🔴 Assertion relaxada (de == "exact" pra .contains("e"))
🔴 Teste desabilitado junto com mudança de código
🟡 Fix veio antes de characterization test
🟡 Interface pública mudou sem menção a blast radius
O que isso muda na prática
IA vai gerar cada vez mais código. A pergunta não é "a IA fez certo?" — é "como sei que fez certo?"
Sem rede de segurança, "testes passando" não significa nada. Com characterization testing + regra anti-gaming, você tem prova verificável de que o comportamento foi preservado.
O LLM é commodity. O harness é o diferencial.