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çãoFalhas detectadas
Remove offset clamping4
Remove guard total > 06
Off-by-one em returned12
isError: true → false19

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/ e Sources/ estão em commits separados?
  • Assertions não foram relaxadas junto com mudança de código?
  • Nenhum @disabled ou 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.