O Perímetro de Confiança: Arquitetando Harnesses de Teste que Isolam o Risco do Código Gerado por IA
Você já revisou um pull request gerado por um agente de IA que passou em 100% da suíte de testes existente e, ainda assim, introduziu uma regressão sutil em produção três semanas depois? O padrão é conhecido por quem trabalha com geração automática de código em escala: os testes que você já tinha foram escritos para validar o comportamento que humanos costumavam implementar, não para capturar as formas específicas — e recorrentes — pelas quais um modelo de linguagem erra. A suíte “verde” deu falsa segurança porque nunca foi desenhada para pegar aquele tipo de falha.
Esse é o problema central que um harness de testes para código gerado por IA precisa resolver, e é diferente do problema que uma suíte de testes tradicional resolve. Não se trata apenas de “ter mais testes”. Trata-se de reconhecer que código gerado por IA tem uma distribuição de erros distinta — alucinação de APIs inexistentes, edge cases plausíveis mas nunca testados, padrões copiados de contextos incompatíveis, dependências implícitas que o modelo assumiu sem verificar — e construir uma infraestrutura de verificação desenhada especificamente para essa distribuição. Neste artigo, vamos além do “escreva mais testes” e entramos em decisões de arquitetura: como estruturar camadas de contenção, que tipo de verificação automatizada realmente pega o que review humano não pega, e onde o custo de manter esse harness supera o benefício.
Por que a suíte de testes que você já tem não é suficiente
A maioria dos times trata “testar código gerado por IA” como sinônimo de “rodar a suíte existente contra o novo código”. Isso é necessário, mas está longe de ser suficiente, por um motivo estrutural: sua suíte de testes atual foi construída incrementalmente, ao longo do tempo, cobrindo os bugs que humanos historicamente introduziram naquele código-base. Ela reflete o perfil de erro dos seus desenvolvedores, não o perfil de erro do modelo que está gerando código agora.
Segundo a NxCode, em seu guia sobre harness engineering, a disciplina de projetar ambientes, restrições e loops de feedback é o que efetivamente torna agentes de codificação produtivos e seguros — não a qualidade isolada do modelo. Isso implica uma mudança de postura: o harness não é um acessório de QA, é parte da arquitetura do sistema de desenvolvimento assistido por IA.
O perfil de erro específico de código gerado por IA
Antes de desenhar qualquer harness, vale mapear as categorias de falha que efetivamente aparecem com geração por IA, porque cada categoria exige uma estratégia de detecção diferente:
- Alucinação de interface: o modelo chama um método que não existe naquela versão da biblioteca, ou inventa uma assinatura plausível baseada em padrões de outras linguagens.
- Contrato implícito quebrado: o código funciona no caminho feliz, mas viola um invariante que nunca estava documentado explicitamente (ordem de chamadas, estado mutável compartilhado, transações).
- Edge case plausível, nunca testado: o modelo produz uma implementação genérica que ignora casos de borda específicos do seu domínio (fuso horário, moeda, unicode, concorrência).
- Copy-paste semântico: um padrão correto em outro contexto (outro serviço, outra stack) aplicado de forma incompatível ao seu sistema.
- Regressão silenciosa em refatoração: a IA reescreve uma função “melhorando” o código, mas altera comportamento de borda que nenhum teste cobria.
Um harness bem desenhado ataca essas categorias uma a uma, com ferramentas distintas — não com “mais testes unitários genéricos”.
Arquitetura em camadas: o modelo de contenção
A abordagem mais robusta que tenho visto funcionar em produção é tratar o harness como um pipeline de camadas de contenção, cada uma com uma responsabilidade única e um custo de execução crescente. A ideia é que código gerado por IA passe por filtros progressivamente mais caros e mais específicos, e falhe o mais cedo possível.
Camada 1 — Verificação estática e de contrato
Antes de qualquer teste de comportamento, valide que o código gerado respeita os contratos estáticos do projeto: tipagem, assinatura de interfaces, ausência de símbolos inexistentes. Isso pega alucinação de API antes mesmo de rodar qualquer coisa.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// verify-contracts.ts import ts from "typescript"; interface ContractViolation { file: string; message: string; line: number; } function verifyGeneratedFile(filePath: string): ContractViolation[] { const program = ts.createProgram([filePath], { strict: true, noImplicitAny: true, target: ts.ScriptTarget.ES2022, }); const diagnostics = ts.getPreEmitDiagnostics(program); const violations: ContractViolation[] = []; for (const diag of diagnostics) { if (diag.file && diag.start !== undefined) { const { line } = diag.file.getLineAndCharacterOfPosition(diag.start); violations.push({ file: diag.file.fileName, message: ts.flattenDiagnosticMessageText(diag.messageText, "\n"), line: line + 1, }); } } return violations; } const violations = verifyGeneratedFile(process.argv[2]); if (violations.length > 0) { console.error(`Bloqueado: ${violations.length} violação(ões) de contrato encontradas.`); violations.forEach(v => console.error(` ${v.file}:${v.line} — ${v.message}`)); process.exit(1); } |
O mesmo princípio se aplica em outras linguagens: em C#, o compilador com TreatWarningsAsErrors e analisadores Roslyn customizados; em Java, checkers do Error Prone; em Delphi, a análise estática do compilador combinada com ferramentas como o Pascal Analyzer para detectar chamadas a métodos inexistentes ou assinaturas incompatíveis introduzidas por geração automática.
Camada 2 — Testes de propriedade, não apenas de exemplo
Testes unitários tradicionais verificam exemplos específicos: “dado X, espero Y”. Isso é fraco contra código gerado por IA porque o modelo tende a acertar exatamente os exemplos óbvios e falhar nos casos que ninguém pensou em escrever manualmente. Testes de propriedade (property-based testing) invertem a lógica: você declara invariantes que devem valer para qualquer entrada, e a ferramenta gera centenas de casos, incluindo os patológicos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from hypothesis import given, strategies as st # Código gerado por IA a ser validado def parse_currency(value: str) -> int: """Converte string de moeda para centavos (inteiro).""" cleaned = value.replace("R$", "").replace(".", "").replace(",", ".").strip() return round(float(cleaned) * 100) @given(st.decimals(min_value="0.01", max_value="999999.99", places=2)) def test_parse_currency_is_reversible(amount): formatted = f"R$ {amount:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".") result = parse_currency(formatted) expected = round(float(amount) * 100) assert result == expected, f"Falhou para {formatted}: esperado {expected}, obtido {result}" @given(st.text()) def test_parse_currency_never_crashes_silently(garbage_input): try: parse_currency(garbage_input) except (ValueError, AttributeError): pass # Falha esperada e controlada except Exception as e: raise AssertionError(f"Exceção não tratada para entrada '{garbage_input}': {type(e)}") |
Esse tipo de teste é particularmente eficiente contra código gerado por IA porque expõe rapidamente os edge cases que o modelo simplesmente não considerou — no exemplo acima, valores negativos, separadores decimais inconsistentes ou entradas vazias tendem a quebrar implementações geradas automaticamente que “parecem” corretas no caminho feliz.
💡 Dica do Mestre: vale a pena assistir ao episódio Harness Engineering: O futuro da engenharia de software com IA, que discute exatamente essa mudança de mentalidade — de “revisar o output” para “projetar o ambiente que restringe o output”.
Camada 3 — Testes de comportamento (behaviour harness)
Property-based testing pega falhas de lógica isolada, mas não pega falhas de comportamento em relação ao sistema como um todo — regressões de fluxo, quebra de contratos entre serviços, efeitos colaterais inesperados. Para isso, a abordagem de “behaviour harness” ganhou tração recentemente: você define especificações de comportamento executáveis, desacopladas da implementação, e roda o código gerado contra elas antes de aceitar o merge.
Conforme mostrado em este vídeo sobre behaviour harness para código gerado por IA, a velocidade de geração de código pela IA não é o gargalo — o gargalo é garantir que essa velocidade não introduza regressões silenciosas em fluxos críticos de negócio.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// BehaviourSpec.cs — especificação executável independente da implementação public class PedidoBehaviourSpec { [Theory] [InlineData(0, false)] // pedido sem itens não pode ser confirmado [InlineData(1, true)] public void PedidoSoPodeSerConfirmadoComItens(int quantidadeItens, bool esperado) { var pedido = new Pedido(); for (int i = 0; i < quantidadeItens; i++) pedido.AdicionarItem(new ItemPedido("SKU-001", 1, 10.0m)); var podeConfirmar = pedido.PodeSerConfirmado(); Assert.Equal(esperado, podeConfirmar); } [Fact] public void ConfirmarPedidoDuasVezesDeveSerIdempotente() { var pedido = new Pedido(); pedido.AdicionarItem(new ItemPedido("SKU-001", 1, 10.0m)); pedido.Confirmar(); var estadoAposConfirmarUmaVez = pedido.Status; pedido.Confirmar(); // código gerado por IA às vezes não trata reentrância var estadoAposConfirmarDuasVezes = pedido.Status; Assert.Equal(estadoAposConfirmarUmaVez, estadoAposConfirmarDuasVezes); } } |
O ponto crítico aqui: essas especificações de comportamento devem ser escritas — ou pelo menos revisadas linha a linha — por humanos, antes de o código ser gerado, seguindo lógica de especificação prévia. Deixar a própria IA gerar tanto o código quanto os testes de comportamento que o validam cria um ciclo fechado onde os mesmos vieses e alucinações do modelo passam despercebidos dos dois lados.
Camada 4 — Sandbox de execução isolada
A última camada de contenção, e a mais cara operacionalmente, é rodar o código gerado em um ambiente isolado antes de qualquer merge — sem acesso a rede externa, com sistema de arquivos efêmero, limites de CPU e memória, e captura completa de syscalls. Isso não substitui os testes anteriores; complementa-os, porque pega comportamentos que nenhuma asserção prevê, como uma chamada de rede não autorizada que o modelo “achou” que era necessária, ou um loop que consome recursos de forma desproporcional.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# harness-sandbox.yml — configuração de execução isolada version: "1" sandbox: network: none filesystem: ephemeral timeout_seconds: 30 limits: cpu: "1" memory: "512Mi" capture: - syscalls - stdout - stderr - exit_code fail_conditions: - network_attempt_detected - filesystem_write_outside_tmp - exit_code_nonzero - timeout_exceeded |
Ferramentas de orquestração de CI como containers efêmeros com políticas de seccomp, ou soluções dedicadas de sandboxing, cumprem esse papel. O importante na decisão arquitetural é: essa camada só deve rodar depois que as camadas 1 a 3 já filtraram o volume óbvio de falhas, porque o custo de infraestrutura de sandboxing isolado é significativamente maior que rodar uma suíte de testes unitários.
Onde o harness ganha inteligência: sinais específicos de geração por IA
Além das quatro camadas de contenção, um harness maduro para código gerado por IA incorpora verificações que não fazem sentido em um pipeline tradicional, porque são desenhadas especificamente para os vieses do modelo.
De acordo com o guia da Cortex Intelligence sobre harness e plataformas de IA, o conceito de harness em contexto de IA está diretamente ligado a transformar inteligência artificial em execução controlada — e controle, nesse sentido, significa também instrumentação de sinais que humanos normalmente não precisariam checar.
Verificação de dependências fantasma
Um script simples, rodado antes mesmo da camada estática, elimina boa parte das alucinações de API mais grosseiras: comparar todo import ou using declarado no código gerado contra o que realmente existe no lockfile ou manifesto de dependências do projeto.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/usr/bin/env bash # check-phantom-deps.sh set -euo pipefail GENERATED_FILE="$1" LOCKFILE="package-lock.json" # Extrai imports do arquivo gerado IMPORTS=$(grep -oP "(?<=from ['\"])[^'\"]+(?=['\"])" "$GENERATED_FILE" | grep -v "^\./" | sort -u) for pkg in $IMPORTS; do ROOT_PKG=$(echo "$pkg" | cut -d'/' -f1) if ! grep -q "\"$ROOT_PKG\"" "$LOCKFILE"; then echo "ALERTA: pacote '$ROOT_PKG' importado mas não encontrado no lockfile." exit 1 fi done echo "Nenhuma dependência fantasma detectada." |
Diff semântico contra a versão anterior
Quando a IA é usada para refatorar código existente, comparar apenas o diff textual esconde mudanças de comportamento. Um harness maduro roda a suíte de testes de comportamento tanto na versão antiga quanto na nova, e compara os resultados byte a byte antes de aceitar que a refatoração é comportamentalmente equivalente — a menos que a mudança de comportamento seja explicitamente o objetivo da tarefa.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# semantic-diff-runner.py import subprocess import json def run_test_suite(commit_ref: str) -> dict: subprocess.run(["git", "checkout", commit_ref], check=True) result = subprocess.run( ["pytest", "--json-report", "--json-report-file=report.json"], capture_output=True ) with open("report.json") as f: return json.load(f) before = run_test_suite("HEAD~1") after = run_test_suite("HEAD") before_outcomes = {t["nodeid"]: t["outcome"] for t in before["tests"]} after_outcomes = {t["nodeid"]: t["outcome"] for t in after["tests"]} regressions = [ nodeid for nodeid, outcome in before_outcomes.items() if outcome == "passed" and after_outcomes.get(nodeid) == "failed" ] if regressions: print(f"Regressão comportamental detectada em {len(regressions)} teste(s):") for r in regressions: print(f" - {r}") exit(1) |
Trade-offs: onde esse investimento vale a pena e onde não vale
Construir as quatro camadas descritas tem custo real de implementação e de manutenção contínua. Vale a pena discutir abertamente quando o investimento se paga e quando é excesso de engenharia.
Quando vale a pena
- Times que já delegam geração de código a agentes de forma recorrente e em volume — não um uso pontual, mas parte do fluxo diário.
- Sistemas com domínio de negócio complexo, onde edge cases de moeda, concorrência ou regras regulatórias são caros de errar.
- Bases de código legadas sendo refatoradas por IA, onde regressões silenciosas são particularmente perigosas por falta de contexto histórico documentado.
Quando não vale a pena
- Protótipos e provas de conceito descartáveis — o custo de manter quatro camadas de verificação supera em muito o valor de um código que será jogado fora em semanas.
- Times pequenos sem capacidade de manter a infraestrutura de sandboxing e os testes de propriedade atualizados — um harness mal mantido gera mais falsos positivos que confiança real, e a equipe passa a ignorá-lo.
- Código gerado em domínios de baixíssimo risco, onde um bug custa uma correção rápida e não um incidente com impacto financeiro ou de segurança.
O erro mais comum que vejo é aplicar o harness completo indiscriminadamente a todo tipo de código gerado, o que cria atrito desproporcional e leva desenvolvedores a contornar o processo. A decisão de quais camadas ativar deveria ser proporcional ao risco do componente sendo gerado — um harness de teste para um script de migração de dados não precisa da mesma sandbox isolada que um serviço de pagamento.
💡 Dica do Mestre: para um roteiro mais detalhado, passo a passo, de construção de um harness aplicado especificamente a código Java, C# ou Delphi, veja o artigo Como Criar um Harness de Testes para Código Gerado por IA, que complementa a discussão arquitetural feita aqui com exemplos de implementação mais extensos.
O custo de manutenção que a documentação oficial não menciona
Um ponto raramente discutido é que o harness em si precisa de manutenção contínua, e essa manutenção tem um perfil diferente da manutenção de uma suíte de testes tradicional. Testes de propriedade exigem revisão periódica das faixas de valores geradas conforme o domínio de negócio evolui. Especificações de comportamento precisam ser atualizadas manualmente sempre que uma regra de negócio muda — e se você delegar essa atualização à mesma IA que gera o código de produção, perde a independência que dá valor ao harness.
Há também o custo de falsos positivos: sandboxes excessivamente restritivas bloqueiam operações legítimas (uma chamada de rede necessária, uma escrita em diretório temporário fora do esperado), e cada falso positivo corrói a confiança da equipe no processo. A calibração dessas políticas de contenção é trabalho contínuo, não configuração de uma vez.
Métrica que importa: taxa de escape, não taxa de cobertura
Cobertura de código continua sendo uma métrica pobre para medir a eficácia de um harness contra código gerado por IA, porque cobertura mede linhas executadas, não comportamentos verificados. A métrica mais reveladora é a taxa de escape: quantos bugs originados em código gerado por IA chegaram a produção apesar de terem passado por todas as camadas do harness. Rastrear essa métrica ao longo do tempo, e fazer um post-mortem de cada escape para identificar qual camada deveria ter pego aquele erro, é o que realmente amadurece o harness — não adicionar testes genéricos.
Junte-se à Comunidade Dev’s AI