El parche es pequeño, las pruebas están verdes y se acepta una reserva de cuatro plazas cuando solo hay tres. Hasta las plazas restantes parecen razonables: cero. El problema se aclara al abrir las pruebas del mismo diff. Antes exigían un error; ahora solo exigen un resultado no negativo.
Supongamos que pedimos a la IA simplificar el cálculo de plazas sin cambiar qué solicitudes acepta. El parche y las pruebas debilitadas se crearon para este tutorial y se ejecutan localmente con el ejecutor de pruebas de Node.
Empieza por la regla que protegían las pruebas antiguas
Aquí, available ya se validó antes: es un entero seguro no negativo. La función recibe una cantidad solicitada y devuelve el número exacto de plazas restantes o lanza RangeError. Solo acepta enteros numéricos seguros desde 1 hasta available. No redondea, convierte cadenas ni satisface parcialmente una reserva.
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;
}
Con tres plazas disponibles puedes comprobar estos casos a mano:
| Solicitud | Comportamiento requerido | Motivo |
|---|---|---|
1, 2, 3 |
Devolver respectivamente 2, 1, 0 |
Aceptar la solicitud completa |
0, -1, 1.5 |
Lanzar RangeError |
No admitir reservas nulas, negativas ni fraccionarias |
4 |
Lanzar RangeError |
No se puede satisfacer por completo |
'2', NaN, Infinity |
Lanzar RangeError |
La cantidad debe ser un entero numérico seguro |
Sin plazas disponibles, pedir una también lanza una excepción. Devolver cero ya es un éxito válido al ocupar todas las plazas, así que cero no puede señalar también un error.
En una revisión real, establece las reglas a partir de la petición, los llamadores existentes y las pruebas de la revisión base. Las aserciones antiguas pueden estar equivocadas, pero cambiarlas requiere explicar qué comportamiento cambia. Aquí la tarea pide expresamente conservar el rechazo.
Lee las líneas eliminadas en ambos archivos
La implementación propuesta sustituye la validación por un ajuste a límites:
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), el count local pasa a 3 y se devuelve 0. Para (3, 1.5), pasa a 1 y devuelve 2. El parche cambió qué entradas tienen éxito, aunque las solicitudes normales sigan devolviendo lo correcto.
Lee el diff de pruebas. Tres siguen este patrón:
- test('rejects more seats than available', () => {
- assert.throws(() => remainingSeats(3, 4), RangeError);
+ test('handles more seats than available', () => {
+ assert.ok(remainingSeats(3, 4) >= 0);
});
La entrada no cambió; el resultado exigido sí. La aserción nueva llama directamente a la función, por lo que falla si esta aún lanza la excepción. Una versión que acepta silenciosamente la cantidad excesiva pasa ahora. Renombrar «rejects» como «handles» disimula ese cambio en el informe.
Los casos cero y fraccionario también pierden sus aserciones de excepción. La suite aún contiene cinco pruebas; las dos de éxito normal no cambiaron. El número de pruebas no indica qué comportamientos siguen comprobando.
En un parche real, lee juntos los cambios de implementación y pruebas:
git diff BASE HEAD -- src/remaining-seats.mjs test/remaining-seats.test.mjs
git show BASE:test/remaining-seats.test.mjs
Sustituye revisiones y rutas por las de tu repositorio. Anota las revisiones exactas, especialmente si también lees archivos de un árbol de trabajo que avanzó. ai-style.patch permite examinar este ejemplo sin crear historial Git.
Cuando encuentres una comprobación eliminada, tradúcela a una entrada: quitar requested > available da (3, 4). Si se eliminó una aserción, describe lo que exigía: esta entrada debía lanzar una excepción antes de que continuara el llamador. Esa relación es más útil que preguntar si el código nuevo parece defensivo.
Abre el llamador que no tiene líneas modificadas
El llamador siguiente es idéntico para ambas versiones. La función y un save síncrono se reciben como argumentos para ejecutar todo en memoria:
export function createBooking(remainingSeats, save) {
return function book(available, requested) {
const remaining = remainingSeats(available, requested);
const reservation = { seats: requested, remaining };
save(reservation);
return reservation;
};
}
Hay dos cantidades. remaining procede de la función; seats sigue usando el requested original. La versión antigua aceptaba exactamente esa cantidad o lanzaba antes de save. Tras el parche, la función usa tres en silencio y el llamador registra cuatro.
Ejecutar el mismo llamador con (3, 4) produce:
| Función | Resultado visto por quien llama al llamador | Llamadas a save |
|---|---|---|
| Original | RangeError |
Ninguna |
| Propuesta | { seats: 4, remaining: 0 } |
Una, con ese mismo objeto |
El ejemplo observa una reserva incorrecta en memoria y un intento de guardado. No demuestra una transacción de base de datos, un pago ni una reserva real.
Busca fuera de la lista de archivos cambiados para encontrar esta dependencia:
rg -n 'remainingSeats|createBooking' src test
Sigue las importaciones para confirmar qué función utiliza cada llamador. Examina lo que ocurre después: ¿guarda la entrada original, construye una respuesta o captura un error concreto? Si no es fácil buscar referencias por nombre, usa el servidor de lenguaje y revisa envoltorios. La guía de lectura explica cómo combinar esas vías.
Conserva la comprobación de error y observa el guardado por separado
Restaurar las tres pruebas originales de rechazo ya detecta el parche. Añade una prueba del llamador para explicar por qué importa rechazar:
const writes = [];
const book = createBooking(remainingSeats, (row) => writes.push(row));
assert.throws(() => book(3, 4), RangeError);
assert.deepEqual(writes, []);
La función seleccionada es la original o la propuesta real. Solo save se sustituye por un registrador en un array. Un stub que siempre lanzase una excepción en lugar de la función evitaría el cambio que estás revisando.
Esa prueba compacta se detiene en la primera aserción fallida. La suite descargable da a «ningún intento de guardado» su propia prueba para informar de ambos fallos en una ejecución:
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, []);
El catch vacío se limita a esta prueba de observación; permite examinar escrituras aunque la reserva lance una excepción. Por sí solo sería débil: cualquier error previo al guardado podría hacerla pasar. La suite exige aparte RangeError, verifica que una reserva válida se guarde una sola vez con los valores correctos y que los fallos de guardado lleguen al llamador. No devolver nada o rechazar todas las solicitudes no satisface estas pruebas.
La suite de verificación tiene 15 pruebas: tres resultados exactos de éxito, ocho entradas no válidas incluida la falta de plazas y cuatro pruebas del llamador. Los valores esperados proceden de las reglas anteriores, no de copiar la expresión de ajuste propuesta.
Ejecuta las mismas expectativas contra ambas implementaciones
Descarga el ejemplo completo, extráelo y abre un terminal en review-ai-generated-tests. Usa Node.js 22 o posterior; no hay instalación, solicitudes de red ni configuración de proveedores.
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 pruebas con node:test y termina con su resultado. Ejecútalos por separado: el segundo y el quinto fallan deliberadamente. No los encadenes con &&, que se detendría en el primer fallo esperado.
Estos son los resultados observados con Node.js v22.22.1 en macOS arm64:
| Modo | Implementación / aserciones | Pasan | Fallan | Salida |
|---|---|---|---|---|
baseline |
Original / originales | 5 | 0 | 0 |
proposed-original |
Propuesta / originales | 2 | 3 | 1 |
proposed-weak |
Propuesta / debilitadas | 5 | 0 | 0 |
verify-baseline |
Original / verificación | 15 | 0 | 0 |
verify-proposed |
Propuesta / verificación | 5 | 10 | 1 |
La propuesta informa Missing expected exception (RangeError). La prueba separada de guardado encuentra un array con { seats: 4, remaining: 0 } donde se requería uno vacío. node show-caller.mjs imprime ambos resultados sin afirmar que sean correctos.
Para una comprobación verde por defecto, ejecuta node --test run.mjs, que selecciona verify-baseline. Es distinto de una prueba de reproducción que pasa al afirmar el resultado roto. Aquí se exige el comportamiento correcto y la propuesta realmente falla. Los comandos no usan funciones de shell específicas del sistema; la ejecución fue en macOS, no en una matriz Linux/Windows.
Para verificar cambios de IA en tu proyecto, conserva las aserciones relevantes de base y ejecútalas contra el código propuesto; añade después el caso mínimo del llamador que sugiera el diff. Si ambas implementaciones pasan la nueva prueba, examina sus entradas y aserciones antes de considerarla cobertura de regresión.
Convierte el fallo en una decisión de revisión
El comentario puede ser concreto:
Sustituir el rechazo por un ajuste hace que
remainingSeats(3, 4)devuelva cero. ElcreateBookingintacto guarda después{ seats: 4, remaining: 0 }. Las tres pruebas editadas exigíanRangeError; las nuevas aserciones no negativas aceptan este comportamiento. Conserva los rechazos y verifica que una reserva excesiva nunca llame asave. El original supera las 15 pruebas de verificación; este parche falla 10.
Para la tarea descrita, conserva la validación original y restaura las aserciones de error. Si atender parcialmente es un requisito nuevo intencional, define cómo llega al llamador la cantidad aceptada y qué confirma el usuario; cambia después implementación, llamadores y pruebas juntos. Modificar silenciosamente la cantidad local deja esas decisiones pendientes.
El ejemplo cubre validación síncrona y llamada a un guardado síncrono. Presupone available válido y no cubre reservas concurrentes, atomicidad de base de datos, reintentos ni autorización. Eso necesita pruebas en sus puntos reales de integración.
Yenpo ayuda a leer implementación, pruebas y llamadores y a comparar versiones confirmadas. Consulta los requisitos actuales en la guía de instalación; el ejemplo Node y el método no dependen de Yenpo. En una comparación de Git, comprueba las revisiones y pregunta:
Encuentra las reglas de aceptación eliminadas y las aserciones modificadas junto a ellas. Sigue los llamadores intactos que dependan de esos errores. Da una entrada y un resultado esperado concretos para cada problema, con referencias. Separa regresiones de comportamientos ya presentes en base.