Teste de Regressão para IA
Padrões de teste especificamente projetados 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 capturar.
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 possui um modo sandbox/mock que pode ser aproveitado para testes sem banco de dados
- Executando
/bug-checkou comandos similares de revisão 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 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 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 existe nos tipos gerados)
→ IA revisou a Correção 1, mas não percebeu o problema no SELECT
Correção 3: Alterado para SELECT *
→ Caminho de produção corrigido, caminho sandbox esquecido
→ IA revisou e PERDEU NOVAMENTE (4ª ocorrência)
Correção 4: Teste capturou instantaneamente na primeira execução PASS:
O padrão: inconsistência entre 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 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 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-chave: 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 ser encontrado (estava 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 por 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 de IA: corrigir o caminho de produção e 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 capturou 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 no Fluxo de Verificação de Bugs
Definição de Comando Personalizado
<!-- .claude/commands/bug-check.md -->
# Verificação de Bugs
## Etapa 1: Testes Automatizados (obrigatório, não pode pular)
Execute estes comandos PRIMEIRO antes de qualquer revisão de código:
npm run test # Conjunto de testes Vitest
npm run build # Verificação de tipos 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 a Etapa 2 apenas se ambos passarem
## Etapa 2: Revisão de Código (revisão pela IA)
1. Consistência entre caminhos sandbox/produção
2. Forma da resposta da API corresponde às expectativas do frontend
3. Completude da cláusula SELECT
4. Tratamento de erros com rollback
5. Condições de corrida em atualizações otimistas
## Etapa 3: Para cada bug corrigido, proponha um teste de regressão
O Fluxo de Trabalho
Usuário: "バグチェックして" (ou "/bug-check")
│
├─ Etapa 1: npm run test
│ ├─ FALHA → Bug encontrado mecanicamente (nenhum julgamento de IA necessário)
│ └─ PASS → Continuar
│
├─ Etapa 2: npm run build
│ ├─ FALHA → Erro de tipo encontrado mecanicamente
│ └─ PASS → Continuar
│
├─ Etapa 3: Revisão de código pela IA (com pontos cegos conhecidos em mente)
│ └─ Descobertas reportadas
│
└─ Etapa 4: Para cada correção, escreva um teste de regressão
└─ A próxima verificação de bugs captura se a correção quebrar
Padrões Comuns de Regressão de IA
Padrão 1: Discrepância entre Caminhos 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 } };
// PASS: 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 capturar isso:
it("sandbox e produção retornam os mesmos campos", async () => {
// No ambiente de teste, o modo sandbox é forçado
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 em 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
// PASS: Use SELECT * ou inclua explicitamente as novas colunas
const { data } = await supabase
.from("users")
.select("*")
.single();
Padrão 3: Vazamento de Estado de Erro
Frequência: Moderado — ao adicionar tratamento de erros a componentes existentes
// FALHA: Estado de erro definido, mas dados antigos não limpos
catch (err) {
setError("Falha ao carregar");
// reservations ainda exibe dados da aba anterior!
}
// PASS: Limpar estado relacionado ao erro
catch (err) {
setReservations([]); // Limpar 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 DB
};
// PASS: Capturar estado anterior e fazer 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 remover");
}
};
Estratégia: Testar Onde os Bugs Foram Encontrados
Não almeje 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 por IA:
- A IA tende a cometer o mesmo tipo de erro repetidamente
- Os bugs se agrupam em áreas complexas (autenticação, lógica de múltiplos caminhos, gerenciamento de estado)
- Uma vez testado, aquela 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 de IA | Estratégia de Teste | Prioridade |
|---|---|---|
| Discrepância sandbox/produção | Afirmar mesma forma de resposta no modo sandbox | Alta |
| Omissão em cláusula SELECT | Afirmar todos os campos obrigatórios na resposta | Alta |
| Vazamento de estado de erro | Afirmar limpeza de estado em caso de erro | Média |
| Rollback ausente | Afirmar estado restaurado em falha de API | Média |
| Type cast mascarando null | Afirmar que 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 a primeira etapa de toda verificação de bugs
- Mantenha os testes rápidos (< 1 segundo total com modo sandbox)
- Nomeie os testes com base no 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
- Almeje porcentagem de cobertura — almeje prevenção de regressão