Le correctif est petit, les tests sont verts et une réservation de quatre places passe alors que trois seulement sont disponibles. Le nombre restant paraît même plausible : zéro. Ouvrez les tests du même diff : auparavant ils exigeaient une erreur, désormais seulement un résultat non négatif.
Supposons qu’on demande à l’IA de simplifier le calcul des places sans changer les demandes acceptées. Le correctif et les tests affaiblis ci-dessous ont été écrits pour ce tutoriel et s’exécutent localement avec le lanceur de tests de Node.
Partir de la règle protégée par les anciens tests
Ici, available a déjà été validé en amont : c’est un entier sûr non négatif. La fonction reçoit une quantité, renvoie le nombre exact de places restantes ou lance RangeError. Elle accepte uniquement des entiers numériques sûrs de 1 à available, sans arrondi, conversion de chaînes ni réservation partielle.
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;
}
Avec trois places disponibles, les exemples se vérifient à la main :
| Demande | Comportement requis | Raison |
|---|---|---|
1, 2, 3 |
Renvoyer respectivement 2, 1, 0 |
Accepter toute la demande |
0, -1, 1.5 |
Lancer RangeError |
Pas de réservation nulle, négative ou fractionnaire |
4 |
Lancer RangeError |
Impossible de satisfaire toute la demande |
'2', NaN, Infinity |
Lancer RangeError |
Quantité obligatoirement entière, numérique et sûre |
Sans place disponible, demander une place lance aussi une erreur. Zéro est déjà un succès valide lorsque tout est réservé : il ne peut donc pas servir aussi de signal d’erreur.
Dans une vraie revue, établissez ces règles à partir du besoin, des appelants et des tests de base. Les anciennes assertions peuvent être fausses, mais les changer exige d’expliquer quel comportement évolue. Ici, la tâche demande explicitement de préserver les rejets.
Lire les lignes supprimées dans les deux fichiers
La proposition remplace la validation par un bornage :
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;
}
Pour (3, 4), le count local devient 3 et le retour vaut 0. Pour (3, 1.5), il devient 1 et la fonction renvoie 2. Les entrées acceptées ont changé, même si les demandes ordinaires restent correctes.
Lisez maintenant le diff des tests. Trois suivent ce modèle :
- test('rejects more seats than available', () => {
- assert.throws(() => remainingSeats(3, 4), RangeError);
+ test('handles more seats than available', () => {
+ assert.ok(remainingSeats(3, 4) >= 0);
});
L’entrée ne change pas, mais le résultat exigé oui. La nouvelle assertion appelle directement la fonction et échoue si elle lance encore une erreur. Une version acceptant silencieusement la demande excessive passe. Renommer « rejects » en « handles » rend le changement moins visible dans le rapport.
Les cas zéro et fractionnaire perdent aussi leurs assertions d’exception. La suite contient toujours cinq tests ; les deux succès ordinaires sont inchangés. Le nombre de tests ne dit pas quels comportements restent vérifiés.
Pour un vrai correctif, lisez ensemble implémentation et tests modifiés :
git diff BASE HEAD -- src/remaining-seats.mjs test/remaining-seats.test.mjs
git show BASE:test/remaining-seats.test.mjs
Remplacez révisions et chemins par ceux de votre dépôt. Notez les révisions exactes, surtout si vous lisez aussi un arbre de travail plus récent. Le fichier ai-style.patch permet d’examiner l’exemple sans créer d’historique Git.
Traduisez un contrôle supprimé en une entrée : retirer requested > available donne (3, 4). Pour une assertion retirée, dites ce qu’elle exigeait : cette entrée devait lancer une erreur avant que l’appelant continue. Ce couple est plus utile que juger si le nouveau code paraît défensif.
Ouvrir l’appelant sans ligne modifiée
L’appelant suivant est identique dans les deux versions. La fonction utilitaire et une fonction save synchrone sont injectées pour que tout reste en mémoire :
export function createBooking(remainingSeats, save) {
return function book(available, requested) {
const remaining = remainingSeats(available, requested);
const reservation = { seats: requested, remaining };
save(reservation);
return reservation;
};
}
Il y a deux quantités. remaining vient de la fonction ; seats utilise toujours le requested original. Avant, la fonction acceptait cette quantité exacte ou lançait avant save. Après, elle utilise silencieusement trois tandis que l’appelant enregistre quatre.
Le même appelant avec (3, 4) produit :
| Fonction | Résultat vu par l’appelant supérieur | Appels de sauvegarde |
|---|---|---|
| Originale | RangeError |
Aucun |
| Proposée | { seats: 4, remaining: 0 } |
Un, avec le même objet |
L’exemple observe une réservation incorrecte en mémoire et une tentative de sauvegarde. Il ne démontre ni transaction de base de données, ni paiement, ni vraie réservation client.
Cherchez au-delà des fichiers modifiés pour trouver cette dépendance dans votre projet :
rg -n 'remainingSeats|createBooking' src test
Suivez les imports pour confirmer la fonction utilisée. Puis inspectez la suite : sauvegarde de l’entrée originale, construction d’une réponse ou capture d’une erreur précise ? Si les références sont difficiles à chercher, utilisez le serveur de langage et vérifiez les enveloppes. Le guide de lecture explique comment combiner ces pistes.
Garder le contrôle d’erreur et observer la sauvegarde séparément
Rétablir les trois tests de rejet originaux détecte déjà le correctif. Ajoutez un test de l’appelant pour montrer pourquoi le rejet compte :
const writes = [];
const book = createBooking(remainingSeats, (row) => writes.push(row));
assert.throws(() => book(3, 4), RangeError);
assert.deepEqual(writes, []);
La fonction choisie est la vraie originale ou proposée. Seule save est remplacée par un enregistreur dans un tableau. Remplacer la fonction par un bouchon lançant toujours une erreur contournerait la modification examinée.
Ce test compact s’arrête à la première assertion échouée. La suite téléchargeable teste aussi séparément « aucune tentative de sauvegarde », afin de signaler les deux échecs en une exécution :
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, []);
Le catch vide est limité à ce test d’observation : il permet d’inspecter les écritures même si la réservation lance une erreur. Seul, il serait trop faible, car toute erreur avant sauvegarde le ferait passer. La suite exige séparément RangeError, vérifie une réservation valide sauvegardée exactement une fois avec les bonnes valeurs et la propagation des échecs de sauvegarde. Ne rien renvoyer ou tout rejeter ne suffit pas.
La suite de vérification comporte 15 tests : trois succès exacts, huit entrées invalides dont le cas complet, et quatre tests d’appelant. Les attentes viennent des règles ci-dessus, pas d’une copie de l’expression de bornage proposée.
Exécuter les mêmes attentes sur les deux implémentations
Téléchargez l’exemple complet, extrayez-le et ouvrez un terminal dans review-ai-generated-tests. Utilisez Node.js 22 ou ultérieur ; aucune installation, requête réseau ni configuration de fournisseur.
node run.mjs baseline
node run.mjs proposed-original
node run.mjs proposed-weak
node run.mjs verify-baseline
node run.mjs verify-proposed
Chaque commande enregistre ses tests avec node:test et sort avec leur résultat. Lancez-les séparément : la deuxième et la cinquième échouent volontairement. Ne les reliez pas par &&, qui arrêterait au premier échec attendu.
Voici les résultats observés avec Node.js v22.22.1 sur macOS arm64 :
| Mode | Implémentation / assertions | Réussis | Échoués | Code de sortie |
|---|---|---|---|---|
baseline |
Originale / originales | 5 | 0 | 0 |
proposed-original |
Proposée / originales | 2 | 3 | 1 |
proposed-weak |
Proposée / affaiblies | 5 | 0 | 0 |
verify-baseline |
Originale / vérification | 15 | 0 | 0 |
verify-proposed |
Proposée / vérification | 5 | 10 | 1 |
La proposition signale Missing expected exception (RangeError).. Le test de sauvegarde montre un tableau contenant { seats: 4, remaining: 0 } alors qu’il devait être vide. node show-caller.mjs affiche les deux résultats sans affirmer qu’ils sont corrects.
Pour un contrôle vert par défaut, lancez node --test run.mjs, qui choisit verify-baseline. Contrairement à un test de reproduction acceptant le résultat cassé, ces tests exigent le bon comportement et la proposition échoue réellement. Les commandes n’utilisent aucune fonction shell propre à un OS ; l’exécution a eu lieu sur macOS, pas sur une matrice Linux/Windows.
Pour vérifier vos changements IA, conservez les assertions de base pertinentes et exécutez-les sur le code proposé, puis ajoutez le plus petit cas d’appelant suggéré par le diff. Si les deux versions passent le nouveau test, inspectez ses entrées et assertions avant d’y voir une couverture de régression.
Transformer l’échec en décision de revue
Un commentaire peut rester concret :
Remplacer le rejet par le bornage fait renvoyer zéro à
remainingSeats(3, 4).createBooking, inchangé, sauvegarde alors{ seats: 4, remaining: 0 }. Les trois tests modifiés exigeaientRangeError; leurs nouvelles assertions non négatives acceptent ce comportement. Préserve les rejets et vérifie qu’une réservation excessive n’appelle jamaissave. L’original passe les 15 tests ; ce correctif en échoue 10.
Pour la tâche donnée, gardez la validation originale et rétablissez les assertions d’erreur. Si la satisfaction partielle est voulue, définissez comment la quantité acceptée atteint l’appelant et ce que confirme l’utilisateur, puis modifiez implémentation, appelants et tests ensemble. Changer silencieusement la quantité locale laisse ces décisions ouvertes.
L’exemple couvre la validation synchrone et l’appel d’une sauvegarde synchrone. Il suppose un available valide et ne couvre ni réservations concurrentes, ni atomicité de base de données, ni nouvelles tentatives, ni autorisation. Ces aspects demandent des tests à leurs véritables points d’intégration.
Yenpo aide à lire implémentation, tests et appelants et à comparer les versions validées. Consultez le guide d’installation pour les prérequis ; l’exemple Node et la méthode ne dépendent pas de Yenpo. Dans une comparaison Git, vérifiez les révisions, puis demandez :
Trouve les règles d’acceptation retirées et les assertions modifiées avec elles. Suis les appelants inchangés dépendant de ces erreurs. Donne pour chaque point une entrée concrète et un résultat attendu avec références source. Sépare les régressions du comportement déjà présent dans la base.