Testes de Regressão com IA
Padrões de teste projetados especificamente para desenvolvimento assistido por IA, onde o mesmo modelo escreve e revisa o código — criando pontos cegos sistemáticos que apenas testes automatizados podem detectar.
Quando Ativar
- O agente de IA (Claude Code, Cursor, Codex) modificou rotas de API ou lógica de backend
- Um bug foi encontrado e corrigido — é necessário evitar reintrodução
- O projeto tem um modo sandbox/mock que pode ser aproveitado para testes sem banco de dados
- Executando comandos
/bug-checkou similares após alterações de código - Existem múltiplos caminhos de código (sandbox vs produção, feature flags, etc.)
O Problema Central
Quando uma IA escreve o código e depois revisa seu próprio trabalho, ela carrega as mesmas suposições em ambas as etapas. Isso cria um padrão de falha previsível:
IA escreve correção → IA revisa correção → IA diz "parece correto" → Bug ainda existe
Exemplo do mundo real (observado em produção):
Correção 1: Adicionado notification_settings à resposta da API
→ Esqueceu de adicionar à consulta SELECT
→ IA revisou e não percebeu (mesmo ponto cego)
Correção 2: Adicionado à consulta SELECT
→ Erro de compilação TypeScript (coluna não está nos tipos gerados)
→ IA revisou a Correção 1 mas não detectou o problema no SELECT
Correção 3: Alterado para SELECT *
→ Corrigiu o caminho de produção, esqueceu o caminho sandbox
→ IA revisou e PERDEU NOVAMENTE (4ª ocorrência)
Correção 4: Teste detectou instantaneamente na primeira execução PASS:
O padrão: inconsistência de caminho sandbox/produção é a regressão nº 1 introduzida por IA.
Teste de API em Modo Sandbox
A maioria dos projetos com arquitetura amigável para IA possui um modo sandbox/mock. Esta é a chave para testes de API rápidos e sem banco de dados.
Configuração (Vitest + Next.js App Router)
// vitest.config.ts
import { defineConfig } from "vitest/config";
import path from "path";
export default defineConfig({
test: {
environment: "node",
globals: true,
include: ["__tests__/**/*.test.ts"],
setupFiles: ["__tests__/setup.ts"],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "."),
},
},
});
// __tests__/setup.ts
// Força o modo sandbox — nenhum banco de dados necessário
process.env.SANDBOX_MODE = "true";
process.env.NEXT_PUBLIC_SUPABASE_URL = "";
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = "";
Helper de Teste para Rotas de API do Next.js
// __tests__/helpers.ts
import { NextRequest } from "next/server";
export function createTestRequest(
url: string,
options?: {
method?: string;
body?: Record<string, unknown>;
headers?: Record<string, string>;
sandboxUserId?: string;
},
): NextRequest {
const { method = "GET", body, headers = {}, sandboxUserId } = options || {};
const fullUrl = url.startsWith("http") ? url : `http://localhost:3000${url}`;
const reqHeaders: Record<string, string> = { ...headers };
if (sandboxUserId) {
reqHeaders["x-sandbox-user-id"] = sandboxUserId;
}
const init: { method: string; headers: Record<string, string>; body?: string } = {
method,
headers: reqHeaders,
};
if (body) {
init.body = JSON.stringify(body);
reqHeaders["content-type"] = "application/json";
}
return new NextRequest(fullUrl, init);
}
export async function parseResponse(response: Response) {
const json = await response.json();
return { status: response.status, json };
}
Escrevendo Testes de Regressão
O princípio fundamental: escreva testes para bugs que foram encontrados, não para código que funciona.
// __tests__/api/user/profile.test.ts
import { describe, it, expect } from "vitest";
import { createTestRequest, parseResponse } from "../../helpers";
import { GET, PATCH } from "@/app/api/user/profile/route";
// Define o contrato — quais campos DEVEM estar na resposta
const REQUIRED_FIELDS = [
"id",
"email",
"full_name",
"phone",
"role",
"created_at",
"avatar_url",
"notification_settings", // ← Adicionado após bug que o encontrou faltando
];
describe("GET /api/user/profile", () => {
it("retorna todos os campos obrigatórios", async () => {
const req = createTestRequest("/api/user/profile");
const res = await GET(req);
const { status, json } = await parseResponse(res);
expect(status).toBe(200);
for (const field of REQUIRED_FIELDS) {
expect(json.data).toHaveProperty(field);
}
});
// Teste de regressão — este bug exato foi introduzido pela IA 4 vezes
it("notification_settings não é undefined (regressão BUG-R1)", async () => {
const req = createTestRequest("/api/user/profile");
const res = await GET(req);
const { json } = await parseResponse(res);
expect("notification_settings" in json.data).toBe(true);
const ns = json.data.notification_settings;
expect(ns === null || typeof ns === "object").toBe(true);
});
});
Testando Paridade Sandbox/Produção
A regressão mais comum da IA: corrigir o caminho de produção, mas esquecer o caminho sandbox (ou vice-versa).
// Testa se as respostas do sandbox correspondem ao contrato esperado
describe("GET /api/user/messages (lista de conversas)", () => {
it("inclui partner_name no modo sandbox", async () => {
const req = createTestRequest("/api/user/messages", {
sandboxUserId: "user-001",
});
const res = await GET(req);
const { json } = await parseResponse(res);
// Isso detectou um bug onde partner_name foi adicionado
// ao caminho de produção, mas não ao caminho sandbox
if (json.data.length > 0) {
for (const conv of json.data) {
expect("partner_name" in conv).toBe(true);
}
}
});
});
Integrando Testes ao Fluxo de Verificação de Bugs
Definição de Comando Personalizado
<!-- .claude/commands/bug-check.md -->
# Verificação de Bug
## Passo 1: Testes Automatizados (obrigatório, não pode pular)
Execute estes comandos PRIMEIRO antes de qualquer revisão de código:
npm run test # Suíte de testes Vitest
npm run build # Verificação de tipo TypeScript + build
- Se os testes falharem → reporte como bug de maior prioridade
- Se o build falhar → reporte erros de tipo como maior prioridade
- Prossiga para o Passo 2 apenas se ambos passarem
## Passo 2: Revisão de Código (Revisão pela IA)
1. Consistência de caminho sandbox/produção
2. Forma da resposta da API corresponde às expectativas do frontend
3. Completude da cláusula SELECT
4. Tratamento de erro com rollback
5. Condições de corrida em atualizações otimistas
## Passo 3: Para cada bug corrigido, proponha um teste de regressão
O Fluxo
Usuário: "バグチェックして" (ou "/bug-check")
│
├─ Passo 1: npm run test
│ ├─ FALHOU → Bug encontrado mecanicamente (nenhum julgamento da IA necessário)
│ └─ PASSOU → Continuar
│
├─ Passo 2: npm run build
│ ├─ FALHOU → Erro de tipo encontrado mecanicamente
│ └─ PASSOU → Continuar
│
├─ Passo 3: Revisão de código pela IA (com pontos cegos conhecidos em mente)
│ └─ Resultados reportados
│
└─ Passo 4: Para cada correção, escreva um teste de regressão
└─ A próxima verificação de bug detecta se a correção quebrar
Padrões Comuns de Regressão com IA
Padrão 1: Discrepância de Caminho Sandbox/Produção
Frequência: Mais comum (observado em 3 de 4 regressões)
// FALHA: IA adiciona campo apenas ao caminho de produção
if (isSandboxMode()) {
return { data: { id, email, name } }; // Novo campo faltando
}
// Caminho de produção
return { data: { id, email, name, notification_settings } };
// PASSOU: Ambos os caminhos devem retornar a mesma forma
if (isSandboxMode()) {
return { data: { id, email, name, notification_settings: null } };
}
return { data: { id, email, name, notification_settings } };
Teste para detectar:
it("sandbox e produção retornam os mesmos campos", async () => {
// No ambiente de teste, o modo sandbox é forçado como LIGADO
const res = await GET(createTestRequest("/api/user/profile"));
const { json } = await parseResponse(res);
for (const field of REQUIRED_FIELDS) {
expect(json.data).toHaveProperty(field);
}
});
Padrão 2: Omissão na Cláusula SELECT
Frequência: Comum com Supabase/Prisma ao adicionar novas colunas
// FALHA: Nova coluna adicionada à resposta, mas não ao SELECT
const { data } = await supabase
.from("users")
.select("id, email, name") // notification_settings não está aqui
.single();
return { data: { ...data, notification_settings: data.notification_settings } };
// → notification_settings é sempre undefined
// PASSOU: Use SELECT * ou inclua explicitamente novas colunas
const { data } = await supabase
.from("users")
.select("*")
.single();
Padrão 3: Vazamento de Estado de Erro
Frequência: Moderada — ao adicionar tratamento de erro a componentes existentes
// FALHA: Estado de erro definido, mas dados antigos não limpos
catch (err) {
setError("Falha ao carregar");
// reservations ainda mostra dados da aba anterior!
}
// PASSOU: Limpa o estado relacionado no erro
catch (err) {
setReservations([]); // Limpa dados obsoletos
setError("Falha ao carregar");
}
Padrão 4: Atualização Otimista Sem Rollback Adequado
// FALHA: Sem rollback em caso de falha
const handleRemove = async (id: string) => {
setItems(prev => prev.filter(i => i.id !== id));
await fetch(`/api/items/${id}`, { method: "DELETE" });
// Se a API falhar, o item desaparece da UI, mas ainda está no BD
};
// PASSOU: Captura o estado anterior e faz rollback em caso de falha
const handleRemove = async (id: string) => {
const prevItems = [...items];
setItems(prev => prev.filter(i => i.id !== id));
try {
const res = await fetch(`/api/items/${id}`, { method: "DELETE" });
if (!res.ok) throw new Error("Erro na API");
} catch {
setItems(prevItems); // Rollback
alert("Falha ao excluir");
}
};
Estratégia: Teste Onde os Bugs Foram Encontrados
Não vise 100% de cobertura. Em vez disso:
Bug encontrado em /api/user/profile → Escreva teste para API de perfil
Bug encontrado em /api/user/messages → Escreva teste para API de mensagens
Bug encontrado em /api/user/favorites → Escreva teste para API de favoritos
Nenhum bug em /api/user/notifications → Não escreva teste (ainda)
Por que isso funciona com desenvolvimento em IA:
- A IA tende a cometer o mesmo tipo de erro repetidamente
- Os bugs se concentram em áreas complexas (auth, lógica de vários caminhos, gerenciamento de estado)
- Uma vez testada, essa regressão exata não pode acontecer novamente
- A contagem de testes cresce organicamente com as correções de bugs — nenhum esforço desperdiçado
Referência Rápida
| Padrão de Regressão com IA | Estratégia de Teste | Prioridade |
|---|---|---|
| Discrepância sandbox/produção | Afirmar a mesma forma de resposta no modo sandbox | Alta |
| Omissão na cláusula SELECT | Afirmar todos os campos obrigatórios na resposta | Alta |
| Vazamento de estado de erro | Afirmar limpeza de estado no erro | Média |
| Rollback ausente | Afirmar estado restaurado em falha da API | Média |
| Máscara de tipo anulando null | Afirmar que o campo não é undefined | Média |
FAÇA / NÃO FAÇA
FAÇA:
- Escreva testes imediatamente após encontrar um bug (antes de corrigi-lo, se possível)
- Teste a forma da resposta da API, não a implementação
- Execute testes como o primeiro passo de toda verificação de bug
- Mantenha os testes rápidos (< 1 segundo no total com modo sandbox)
- Nomeie os testes com o bug que eles previnem (ex.: "regressão BUG-R1")
NÃO FAÇA:
- Escreva testes para código que nunca teve um bug
- Confie na auto-revisão da IA como substituto para testes automatizados
- Pule o teste do caminho sandbox porque "são apenas dados mock"
- Escreva testes de integração quando testes unitários são suficientes
- Mire na porcentagem de cobertura — mire na prevenção de regressão