O patch é pequeno, os testes estão verdes e uma reserva de quatro lugares é aceita quando só há três. Até a contagem restante parece razoável: zero. O problema fica claro ao abrir os testes no mesmo diff. Antes exigiam um erro; agora exigem apenas resultado não negativo.
Suponha que pedimos à IA para simplificar o cálculo de lugares sem mudar quais pedidos são aceitos. O patch e os testes enfraquecidos foram criados para este tutorial e rodam localmente com o executor de testes do Node.
Comece pela regra protegida pelos testes antigos
Neste exemplo, available já foi validado antes: é um inteiro seguro não negativo. A função recebe a quantidade solicitada e retorna exatamente os lugares restantes ou lança RangeError. Aceita apenas inteiros numéricos seguros de 1 até available. Não arredonda, converte strings nem atende reservas parcialmente.
export function remainingSeats(available, requested) {
if (
!Number.isSafeInteger(requested) ||
requested < 1 ||
requested > available
) {
throw new RangeError(
'requested seats must be an integer from 1 to available',
);
}
return available - requested;
}
Com três lugares disponíveis, os exemplos podem ser conferidos à mão:
| Solicitado | Comportamento exigido | Motivo |
|---|---|---|
1, 2, 3 |
Retornar 2, 1, 0, respectivamente |
Aceitar o pedido inteiro |
0, -1, 1.5 |
Lançar RangeError |
Sem reservas nulas, negativas ou fracionárias |
4 |
Lançar RangeError |
Não é possível atender o pedido inteiro |
'2', NaN, Infinity |
Lançar RangeError |
A quantidade deve ser um inteiro numérico seguro |
Sem lugares disponíveis, pedir um também lança erro. Retornar zero já é um sucesso válido para uma reserva exata, então zero não pode servir também como sinal de erro.
Em uma revisão real, estabeleça as regras pelo comportamento solicitado, chamadores existentes e testes da base. Asserções antigas podem estar erradas, mas alterá-las exige explicar qual comportamento muda. Aqui, a tarefa pede explicitamente preservar as rejeições.
Leia as linhas removidas nos dois arquivos
A proposta substitui a validação por limitação do valor:
export function remainingSeats(available, requested) {
- if (
- !Number.isSafeInteger(requested) ||
- requested < 1 ||
- requested > available
- ) {
- throw new RangeError(
- 'requested seats must be an integer from 1 to available',
- );
- }
- return available - requested;
+ const count = Math.min(available, Math.max(1, Math.trunc(requested)));
+ return available - count;
}
Para (3, 4), o count local vira 3 e o retorno é 0. Para (3, 1.5), vira 1 e a função retorna 2. O patch mudou quais entradas têm sucesso, embora pedidos comuns ainda retornem corretamente.
Agora leia o diff dos testes. Três seguem este padrão:
- test('rejects more seats than available', () => {
- assert.throws(() => remainingSeats(3, 4), RangeError);
+ test('handles more seats than available', () => {
+ assert.ok(remainingSeats(3, 4) >= 0);
});
A entrada não mudou; o resultado exigido, sim. A nova asserção executa a função diretamente, portanto falha se ela ainda lança erro. Uma versão que aceita silenciosamente o pedido excessivo agora passa. Trocar “rejects” por “handles” torna a mudança menos visível no relatório.
Os casos zero e fracionário também perdem as asserções de exceção. A suíte continua com cinco testes; os dois sucessos comuns não mudam. A contagem não revela quais comportamentos ainda são verificados.
Em um patch real, leia mudanças da implementação e dos testes juntas:
git diff BASE HEAD -- src/remaining-seats.mjs test/remaining-seats.test.mjs
git show BASE:test/remaining-seats.test.mjs
Substitua revisões e caminhos pelos do seu repositório. Registre as revisões exatas, especialmente se também lê arquivos de uma árvore de trabalho que avançou. O ai-style.patch do download permite examinar o exemplo sem criar histórico Git.
Ao encontrar uma verificação removida, traduza-a em uma entrada: remover requested > available sugere (3, 4). Ao encontrar uma asserção removida, diga o que exigia: a entrada precisava lançar erro antes de o chamador continuar. Esse par é mais útil do que perguntar se o código parece defensivo.
Abra o chamador que não tem linhas alteradas
O chamador abaixo é compartilhado, sem mudanças, pelas duas versões. A função auxiliar e uma função save síncrona são injetadas para manter o exemplo inteiro em memória:
export function createBooking(remainingSeats, save) {
return function book(available, requested) {
const remaining = remainingSeats(available, requested);
const reservation = { seats: requested, remaining };
save(reservation);
return reservation;
};
}
Há duas quantidades. remaining vem da função; seats ainda usa o requested original. A função antiga aceitava exatamente essa quantidade ou lançava antes de save. Após o patch, a função usa três silenciosamente enquanto o chamador registra quatro.
O mesmo chamador com (3, 4) produz:
| Função auxiliar | Resultado visto pelo chamador externo | Chamadas de gravação |
|---|---|---|
| Original | RangeError |
Nenhuma |
| Proposta | { seats: 4, remaining: 0 } |
Uma, com o mesmo objeto |
O exemplo observa uma reserva incorreta em memória e uma tentativa de gravação. Não demonstra uma transação de banco, pagamento ou reserva real de cliente.
Busque além da lista de arquivos alterados para encontrar a dependência no seu projeto:
rg -n 'remainingSeats|createBooking' src test
Siga imports para confirmar qual função o chamador usa. Examine o que acontece depois: salva a entrada original, constrói uma resposta ou captura um erro específico? Se não for fácil buscar referências pelo nome, use o servidor de linguagem e confira wrappers. O guia de leitura explica como combinar esses caminhos.
Preserve a verificação de erro e observe a gravação separadamente
Restaurar os três testes originais de rejeição já detecta o patch. Adicione um teste do chamador para mostrar por que rejeitar importa:
const writes = [];
const book = createBooking(remainingSeats, (row) => writes.push(row));
assert.throws(() => book(3, 4), RangeError);
assert.deepEqual(writes, []);
A função selecionada é a original ou a proposta real. Apenas save é substituída por um registrador em array. Substituir a função por um stub que sempre lança ignoraria justamente a mudança revisada.
Esse teste compacto para na primeira asserção que falha. A suíte do download também testa “nenhuma tentativa de gravação” separadamente, permitindo relatar ambas as falhas em uma execução:
const writes = [];
const book = createBooking(remainingSeats, (row) => writes.push(row));
try {
book(3, 4);
} catch {
// A separate test checks that the error is RangeError.
}
assert.deepEqual(writes, []);
O catch vazio fica restrito a esse teste de observação; permite examinar gravações mesmo quando a reserva lança. Sozinho, seria fraco: qualquer erro antes de salvar o faria passar. A suíte exige RangeError separadamente, verifica que uma reserva válida é salva exatamente uma vez com valores corretos e que falhas de gravação chegam ao chamador. Não retornar nada ou rejeitar todo pedido não satisfaz esses testes.
A suíte de verificação tem 15 testes: três resultados exatos de sucesso, oito entradas inválidas incluindo lotação esgotada e quatro testes do chamador. Os valores esperados vêm das regras acima, não de copiar a expressão de limitação proposta para o teste.
Execute as mesmas expectativas nas duas implementações
Baixe o exemplo completo, extraia e abra um terminal em review-ai-generated-tests. Use Node.js 22 ou posterior; não há instalação, rede nem configuração de provedor.
node run.mjs baseline
node run.mjs proposed-original
node run.mjs proposed-weak
node run.mjs verify-baseline
node run.mjs verify-proposed
Cada comando registra testes com node:test e sai com o resultado. Execute separadamente: o segundo e o quinto falham deliberadamente. Não una a sequência com &&, que pararia na primeira falha esperada.
Estes são os resultados observados com Node.js v22.22.1 no macOS arm64:
| Modo | Implementação / asserções | Passaram | Falharam | Código de saída |
|---|---|---|---|---|
baseline |
Original / originais | 5 | 0 | 0 |
proposed-original |
Proposta / originais | 2 | 3 | 1 |
proposed-weak |
Proposta / enfraquecidas | 5 | 0 | 0 |
verify-baseline |
Original / verificação | 15 | 0 | 0 |
verify-proposed |
Proposta / verificação | 5 | 10 | 1 |
A proposta relata Missing expected exception (RangeError).. O teste separado de gravação relata um array com { seats: 4, remaining: 0 } onde se exigia array vazio. node show-caller.mjs imprime ambos os resultados sem afirmar que algum está correto.
Para uma verificação padrão verde, execute node --test run.mjs; ele seleciona verify-baseline. Isso difere de um teste de reprodução que passa ao afirmar o resultado quebrado. Aqui, os testes exigem comportamento correto, e a proposta realmente falha. Os comandos não usam recursos shell específicos do sistema; a execução foi no macOS, não em uma matriz Linux/Windows.
Para verificar mudanças da IA no seu projeto, preserve as asserções relevantes da base e execute contra a proposta; depois adicione o menor caso de chamador sugerido pelo diff. Se ambas as implementações passarem no novo teste, examine entradas e asserções antes de considerá-lo cobertura de regressão.
Transforme a falha em uma decisão de revisão
Um comentário pode ser concreto:
Substituir rejeição por limitação faz
remainingSeats(3, 4)retornar zero. OcreateBookinginalterado salva{ seats: 4, remaining: 0 }. Os três testes editados antes exigiamRangeError; as novas asserções de não negatividade aceitam o comportamento. Preserve as rejeições e verifique que uma reserva excessiva nunca chamasave. A versão original passa nos 15 testes; o patch falha em 10.
Para a tarefa declarada, mantenha a validação original e restaure as asserções de erro. Se atender parcialmente for um requisito novo intencional, defina como a quantidade aceita chega ao chamador e o que o usuário confirma; altere implementação, chamadores e testes juntos. Mudar silenciosamente a quantidade local deixa essas decisões em aberto.
O exemplo cobre validação síncrona e chamada a uma gravação síncrona. Presume available válido e não cobre reservas concorrentes, atomicidade do banco, novas tentativas ou autorização. Isso exige testes nos pontos reais de integração.
O Yenpo ajuda a ler implementação, testes e chamadores e a comparar versões commitadas. Consulte os requisitos atuais no guia de instalação; o exemplo Node e o método não dependem do Yenpo. Em uma comparação Git, confira as revisões e pergunte:
Encontre as regras de aceitação removidas por este patch e as asserções alteradas junto delas. Siga os chamadores inalterados que dependem desses erros. Para cada preocupação, dê uma entrada concreta e um resultado esperado, com referências. Separe regressões de comportamentos já presentes na base.