Chapitre 1123 minIntermédiaire
Maintenance corrective et évolutive à l'E6 SLAM : cas corrigés
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- relier chaque sous-compétence de maintenance à une preuve visible ;
- organiser l'heure de réalisation d'une correction ou d'une évolution ;
- présenter un correctif et ses tests pendant la recette ;
- répondre aux questions de la commission sur ta démarche.
3 exercices corrigés
À l'épreuve E6 du BTS SIO SLAM, la commission te remet une expression des besoins sur l'une de tes deux réalisations professionnelles : le plus souvent, une correction ou une évolution à apporter à ton application, en une heure de réalisation, suivie d'une recette où tu prouves ta solution. C'est la compétence « Assurer la maintenance corrective ou évolutive d'une solution applicative » mise en situation. Ce chapitre relie chaque sous-compétence à une preuve que la commission peut voir, donne une méthode pour l'heure de réalisation, puis déroule deux cas types corrigés : une correction en PHP et une évolution en JavaScript.
Ce que la commission évalue
L'épreuve dure 1 h 30 de préparation et 40 minutes d'interrogation, en deux phases : 30 minutes pour analyser l'expression des besoins puis 20 minutes d'entretien, une heure de réalisation puis 20 minutes de recette et d'entretien d'explicitation. Le déroulement complet est détaillé dans le cours Épreuve E6 SLAM. Pour la maintenance, la commission cherche cinq preuves, une par sous-compétence :
| Sous-compétence | Ce que la commission veut voir | La preuve à montrer en moins de dix secondes |
|---|---|---|
| Recueillir, analyser et mettre à jour les informations sur une version | Tu sais quelle version tu modifies et tu en livres une nouvelle, documentée | Le tag de départ, l'entrée de changelog, le numéro de la nouvelle version |
| Évaluer la qualité | Tu repères ce qui pourrait être amélioré, et tu le fais ou le notes | Une duplication supprimée, un avertissement du linter corrigé, un ticket de dette créé |
| Analyser et corriger un dysfonctionnement | Le bug est reproduit, sa cause expliquée, sa correction ciblée | Le test rouge puis vert, le diff de la correction, la cause en une phrase |
| Mettre à jour les documentations | La documentation suit le code | Le README, la documentation de l'API ou le dictionnaire de données modifiés |
| Élaborer et réaliser les tests des éléments mis à jour | Les éléments modifiés sont testés, et rien n'a régressé | Le nouveau test, la suite complète verte |
Les indicateurs de la grille ajoutent trois exigences que la commission vérifie pendant la recette : la modélisation existante est mise à jour (MCD, diagramme de classes, voir le chapitre 2), l'interface modifiée respecte les contraintes ergonomiques, et l'application modifiée reste « opérationnelle et stable ». Une évolution qui marche mais casse l'écran voisin ne remplit pas la demande. Les indicateurs exacts de chaque sous-compétence sont repris dans le chapitre compétences du bloc 2.
La méthode pour une correction en une heure
| Temps | Ce que tu fais | Ce qui reste comme preuve |
|---|---|---|
| 0 à 5 min | Créer la branche, lancer la suite de tests pour connaître l'état de départ | git switch -c fix/…, une suite verte au départ |
| 5 à 15 min | Reproduire le bug, puis écrire le test qui le reproduit | Un test rouge, commité ou au moins visible |
| 15 à 35 min | Localiser la cause (stack trace, point d'arrêt), corriger la cause | Le diff de la correction |
| 35 à 45 min | Le nouveau test passe ; toute la suite passe ; vérification à la main dans l'interface | La suite verte, les captures si besoin |
| 45 à 55 min | Commit fix, entrée de changelog, documentation | L'historique, CHANGELOG.md, le README ou la doc d'API |
| 55 à 60 min | Préparer la recette : les étapes de démonstration, les commandes à lancer | Une note de trois lignes |
Pour une évolution, le schéma est le même, avec plus de temps pour l'analyse (déjà largement faite pendant la phase 1) et un découpage en tranches : d'abord la plus petite version utile, testée, puis les améliorations si le temps le permet. Le chapitre phase 2 du cours sur l'épreuve détaille l'organisation de l'heure.
Ce que tu montres pendant la recette
La recette dure 20 minutes au maximum, questions comprises. Prépare un enchaînement de trois minutes :
- Le besoin, en une phrase, et ce que tu as livré.
- La démonstration dans l'application : le scénario de l'expression des besoins, qui fonctionne maintenant.
- Les preuves : le test (et son état rouge avant la correction, que l'historique Git montre), la suite complète verte, le diff, le changelog, la documentation.
- Ce qui reste, honnêtement : un cas non traité, une amélioration notée dans un ticket.
Puis laisse la commission tester elle-même : c'est prévu par le règlement, et c'est là qu'elle essaiera les cas limites. Le chapitre recette du cours sur l'épreuve donne les questions les plus fréquentes.
Cas type 1 : une correction sur Sorbier Véhicules
L'expression des besoins.
Les commerciaux signalent que certaines réservations sont refusées alors que le véhicule est libre. Exemple : la Clio GA-112-BC est réservée le jeudi 19 novembre 2026 de 8 h à 14 h ; une demande de 14 h à 16 h le même jour est refusée avec le message « Ce véhicule n'est pas disponible sur ce créneau ». Corriger ce dysfonctionnement, prouver la correction et mettre à jour la documentation.
Phase 1 : l'analyse à présenter en 20 minutes.
- Reformulation. Un créneau qui commence exactement à l'heure où le précédent se termine doit être accepté. C'est une maintenance corrective : l'application ne respecte pas la règle « un véhicule rendu à 14 h est disponible à 14 h ».
- Hypothèse de cause. Le test de chevauchement utilise des comparaisons larges (
<=,>=) au lieu de comparaisons strictes : deux créneaux qui se touchent sont considérés comme chevauchants. - Impact. La règle est-elle écrite à un seul endroit ? À vérifier dans le service
Disponibiliteet dans la requête SQL du dépôt qui cherche les réservations en conflit. - Démarche. Branche
fix/chevauchement, test PHPUnit qui reproduit le cas du ticket, correction, suite complète, changelog, version2.1.1. - Outils. PHPUnit, Xdebug si la cause n'est pas celle supposée, Git.
Phase 2 : la réalisation.
La lecture du code confirme l'hypothèse, et révèle que la règle est écrite deux fois :
<?php
// src/Service/Disponibilite.php (avant)
declare(strict_types=1);
namespace Sorbier\Vehicules\Service;
use DateTimeImmutable;
final class Disponibilite
{
public static function chevauche(
DateTimeImmutable $debutA,
DateTimeImmutable $finA,
DateTimeImmutable $debutB,
DateTimeImmutable $finB,
): bool {
return $debutA <= $finB && $finA >= $debutB;
}
}
<?php
// src/Modele/ReservationRepository.php (avant, extrait)
$requete = $this->pdo->prepare(
"SELECT COUNT(*) FROM reservation
WHERE id_vehicule = :vehicule AND statut <> 'annulee'
AND debut <= :fin AND fin >= :debut"
);
D'abord le test, avec plusieurs cas grâce à un fournisseur de données (DataProvider) :
<?php
// tests/DisponibiliteTest.php
declare(strict_types=1);
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Sorbier\Vehicules\Service\Disponibilite;
final class DisponibiliteTest extends TestCase
{
public static function creneaux(): array
{
return [
'créneaux qui se suivent (le bug)' => ['08:00', '14:00', '14:00', '16:00', false],
'chevauchement partiel' => ['08:00', '14:30', '14:00', '16:00', true],
'un créneau contient l\'autre' => ['08:00', '18:00', '14:00', '16:00', true],
'créneaux séparés' => ['08:00', '10:00', '14:00', '16:00', false],
];
}
#[DataProvider('creneaux')]
public function testChevauchement(string $debutA, string $finA, string $debutB, string $finB, bool $attendu): void
{
$jour = '2026-11-19 ';
$this->assertSame($attendu, Disponibilite::chevauche(
new DateTimeImmutable($jour . $debutA),
new DateTimeImmutable($jour . $finA),
new DateTimeImmutable($jour . $debutB),
new DateTimeImmutable($jour . $finB),
));
}
}
Le premier cas échoue (la méthode renvoie true), les trois autres passent : ils protègent ce qui marchait. Puis la correction, aux deux endroits, avec la même règle : deux créneaux se chevauchent si chacun commence strictement avant la fin de l'autre.
<?php
// src/Service/Disponibilite.php (après)
return $debutA < $finB && $debutB < $finA;
<?php
// src/Modele/ReservationRepository.php (après, extrait)
$requete = $this->pdo->prepare(
"SELECT COUNT(*) FROM reservation
WHERE id_vehicule = :vehicule AND statut <> 'annulee'
AND debut < :fin AND fin > :debut"
);
Les quatre cas passent, et toute la suite aussi. La vérification à la main dans l'interface reprend le scénario exact de l'expression des besoins. Puis la livraison :
git add tests/DisponibiliteTest.php
git commit -m "test(reservation): reproduit le refus d'un créneau qui suit le précédent"
git add src/Service/Disponibilite.php src/Modele/ReservationRepository.php
git commit -m "fix(reservation): un créneau peut commencer à l'heure de fin du précédent"
git commit -am ne suffirait pas : l'option -a n'ajoute que les fichiers déjà suivis par Git, et le fichier de test, nouveau, resterait hors du commit. Sur ta branche d'examen, ce commit de test encore rouge sert de preuve ; en entreprise, on le regroupe souvent avec la correction (chapitre 5), pour que chaque commit de main garde la suite verte.
## [2.1.1] - 2026-11-19
### Corrigé
- Une réservation commençant à l'heure exacte du retour précédent était refusée.
La documentation : le guide d'utilisation précise désormais qu'un véhicule rendu à 14 h peut être réservé dès 14 h. Et une remarque de qualité pour la recette : la règle de chevauchement était dupliquée entre PHP et SQL, d'où le risque de n'en corriger qu'une. Tu proposes, dans un ticket, que le service s'appuie uniquement sur la requête (ou l'inverse), avec un test d'intégration qui couvre la requête.
Les questions de la commission, et des réponses possibles.
| Question | Réponse attendue |
|---|---|
| Pourquoi des comparaisons strictes ? | Les créneaux sont des intervalles fermés au début et ouverts à la fin : un véhicule rendu à 14 h est libre à 14 h. Deux créneaux qui se touchent ne se chevauchent pas. |
| Comment savez-vous que vous n'avez rien cassé ? | Les trois cas qui passaient avant passent toujours, et toute la suite est verte. J'ai aussi refait le scénario à la main. |
| Pourquoi avoir modifié deux fichiers ? | La règle était écrite en PHP et en SQL. Corriger l'un seulement aurait laissé le bug dans l'autre chemin. J'ai noté la duplication dans un ticket. |
| Que se passe-t-il si un véhicule est rendu en retard ? | Le retard n'est pas géré aujourd'hui : la réservation suivante est acceptée sur la base de l'heure prévue. C'est une évolution à proposer (heure de retour effective, alerte), hors du périmètre de la demande. |
| Quel numéro de version ? | 2.1.1 : une correction rétrocompatible. |
Cas type 2 : une évolution sur Sorbier Frais
L'expression des besoins.
Le service comptable souhaite repérer d'un coup d'œil les notes clôturées en retard. Une note du mois M doit être clôturée au plus tard le 10 du mois M+1. Dans la liste des notes du comptable, signaler les notes clôturées hors délai. Les notes non clôturées ne sont pas concernées.
Phase 1 : l'analyse.
- Reformulation et critères d'acceptation. Une note d'octobre clôturée le 10 novembre est dans les délais ; clôturée le 11 novembre, elle est hors délai ; une note de décembre a jusqu'au 10 janvier de l'année suivante ; une note non clôturée n'a pas de badge ; l'heure de clôture ne compte pas.
- Type. Maintenance évolutive, additive : un champ ajouté à la réponse de l'API, un badge dans le front. Version
1.6.0. - Impact. Le dépôt (la date de clôture doit arriver au bon format), un nouveau service de calcul, la réponse de
GET /api/notes, le composant de liste du front, les tests, la documentation de l'API et le guide du comptable. La base ne change pas. - Choix technique. Le calcul en JavaScript, dans une fonction pure facile à tester unitairement, plutôt qu'en SQL. La règle des 10 jours dans une constante.
Phase 2 : la réalisation, en tranches.
Tranche 1 : la règle et ses tests. Les dates sont manipulées au format texte AAAA-MM-JJ, qui se compare correctement dans l'ordre alphabétique, et la date limite est calculée en temps universel (Date.UTC) pour ne pas dépendre du fuseau horaire du serveur :
// services/delais.js
const DELAI_CLOTURE_JOURS = 10; // jours après la fin du mois
// Date limite de clôture d'une note du mois 'AAAA-MM' : le 10 du mois suivant, au format 'AAAA-MM-JJ'.
function dateLimiteCloture(mois) {
const [annee, m] = mois.split('-').map(Number);
// Date.UTC compte les mois à partir de 0 : m désigne donc déjà le mois suivant.
const limite = new Date(Date.UTC(annee, m, DELAI_CLOTURE_JOURS));
return limite.toISOString().slice(0, 10);
}
// dateCloture : 'AAAA-MM-JJ' (heure éventuelle ignorée) ou null si la note n'est pas clôturée.
function estHorsDelai(mois, dateCloture) {
if (dateCloture === null) return false;
return dateCloture.slice(0, 10) > dateLimiteCloture(mois);
}
module.exports = { dateLimiteCloture, estHorsDelai };
// tests/delais.test.js
const { dateLimiteCloture, estHorsDelai } = require('../services/delais');
describe('délai de clôture', () => {
test('la date limite est le 10 du mois suivant', () => {
expect(dateLimiteCloture('2026-10')).toBe('2026-11-10');
});
test('décembre passe en janvier de l\'année suivante', () => {
expect(dateLimiteCloture('2026-12')).toBe('2027-01-10');
});
test('une note clôturée le 10 est dans les délais', () => {
expect(estHorsDelai('2026-10', '2026-11-10')).toBe(false);
});
test('une note clôturée le 11 est hors délai', () => {
expect(estHorsDelai('2026-10', '2026-11-11')).toBe(true);
});
test('l\'heure de clôture ne change rien', () => {
expect(estHorsDelai('2026-10', '2026-11-10 23:59:00')).toBe(false);
});
test('une note pas encore clôturée n\'est jamais hors délai', () => {
expect(estHorsDelai('2026-10', null)).toBe(false);
});
});
Les six tests passent, y compris quand on lance la suite avec un autre fuseau horaire (TZ=America/Martinique npm test), ce qui prouve que le calcul ne dépend pas de l'heure locale.
Tranche 2 : l'API. Le pilote mysql2 renvoie les colonnes DATETIME sous forme d'objets Date JavaScript ; la requête formate donc la date en texte, et le service ajoute le champ calculé :
// repositories/notes.repository.js (extrait)
const [notes] = await pool.execute(
`SELECT n.id_note, n.mois, n.etat,
DATE_FORMAT(n.date_cloture, '%Y-%m-%d') AS date_cloture
FROM note_frais n
ORDER BY n.mois DESC`,
);
// services/notes.js (extrait)
const { estHorsDelai } = require('./delais');
function pourListe(note) {
return { ...note, horsDelai: estHorsDelai(note.mois, note.date_cloture) };
}
Le champ horsDelai s'ajoute à ceux qui existaient : les clients actuels de l'API l'ignorent, rien ne casse. Un test d'intégration vérifie que la route renvoie bien le champ pour une note en retard, et que les anciens champs n'ont pas bougé.
Tranche 3 : le front. Une ligne dans le composant de liste :
{note.horsDelai && <span className="badge badge-alerte">Hors délai</span>}
Tranche 4 : la documentation. Le champ horsDelai dans la documentation de l'API (type booléen, règle de calcul), une phrase dans le guide du comptable, et l'entrée de changelog :
## [1.6.0] - 2026-11-30
### Ajouté
- Badge « Hors délai » dans la liste des notes du comptable, pour les notes
clôturées après le 10 du mois suivant (#257).
Les questions de la commission, et des réponses possibles.
| Question | Réponse attendue |
|---|---|
| Pourquoi comparer des chaînes plutôt que des dates ? | Au format AAAA-MM-JJ, l'ordre alphabétique est l'ordre chronologique. On évite les objets Date et leurs pièges de fuseau horaire, et le test reste lisible. |
Pourquoi Date.UTC ? | Pour calculer le 10 du mois suivant sans dépendre du fuseau du serveur. Les tests passent avec un autre fuseau horaire. |
| Pourquoi ne pas avoir fait le calcul en SQL ? | C'était possible, avec LAST_DAY et DATE_ADD. J'ai préféré une fonction pure, testable unitairement en quelques millisecondes ; la règle est aussi plus facile à faire évoluer. |
| Et si le délai passe à 15 jours ? | Une seule constante à modifier, DELAI_CLOTURE_JOURS, et les tests à adapter. |
| Votre évolution casse-t-elle quelque chose ? | Non : le champ est ajouté, aucun n'est modifié. Toute la suite de tests est verte, et la version est mineure, 1.6.0. |
Les erreurs qui coûtent des points
- Corriger sans avoir reproduit, et ne pas pouvoir montrer que le bug existait.
- Aucun test, ou un test écrit après coup qui n'a jamais été rouge.
- Corriger le symptôme (un
try … catchqui masque l'erreur) et ne pas savoir expliquer la cause. - Mélanger la correction, un refactoring et une petite évolution dans le même commit.
- Sortir du périmètre : refaire tout l'écran alors que la demande portait sur un badge.
- Oublier l'existant : une évolution qui casse les anciennes données ou un écran voisin.
- Laisser la documentation de côté : pas de changelog, un README faux.
- Ne pas savoir dire ce qui manque : mieux vaut annoncer un cas limite non traité que le laisser découvrir à la commission.
Le chapitre erreurs qui coûtent des points du cours sur l'épreuve complète cette liste pour toute l'E6.
Exercices
Exercice 1 sur 3 Planifier une évolution en une heure Réussi À revoir
Expression des besoins, sur Sorbier Véhicules : « Le responsable du parc veut voir, sur la fiche de chaque véhicule, le nombre total de kilomètres parcourus pendant les réservations du mois en cours. »
Rédige ton plan pour l'heure de réalisation : type de maintenance, critères d'acceptation, découpage en tranches avec un temps pour chacune, tests prévus, documentation à mettre à jour, numéro de version.
Voir la réponseMasquer la réponse de l'exercice 1
- Type : évolutive, additive. Version mineure (
2.2.0depuis2.1.1). - Critères d'acceptation : le total additionne
km_retour - km_departdes réservations du véhicule dont le début est dans le mois en cours ; les réservations annulées sont exclues ; une réservation pas encore rendue (sanskm_retour) n'est pas comptée ; un véhicule sans réservation affiche 0 km ; les autres informations de la fiche ne changent pas. - Tranches :
- (20 min) la méthode de calcul dans le service, avec ses tests PHPUnit : cas courant, annulée exclue, non rendue exclue, aucune réservation ;
- (15 min) la requête du dépôt, qui charge les réservations du mois pour un véhicule (
debut >= premier jour du mois AND debut < premier jour du mois suivant) ; - (10 min) l'affichage sur la fiche, avec vérification à la main ;
- (10 min) suite complète, commit
feat, changelog, guide d'utilisation ; - (5 min) préparation de la recette.
- Documentation : changelog (Ajouté), guide d'utilisation (ce que signifie le total, quelles réservations sont comptées), mise à jour du diagramme de classes si une méthode est ajoutée.
La règle « réservations dont le début est dans le mois » est une décision à annoncer en phase 1 : une réservation à cheval sur deux mois pourrait aussi être répartie. La commission appréciera que tu aies vu la question.
Tu avais trouvé ?
Exercice 2 sur 3 Quelle preuve pour quelle sous-compétence ? Réussi À revoir
Pendant ta recette, tu montres les éléments suivants. Associe chacun à la sous-compétence de maintenance qu'il prouve le mieux.
- L'historique Git, où le test du bug apparaît dans un commit avant la correction.
- Le fichier
CHANGELOG.mdavec la section[2.1.1]. - La sortie de
composer test: 48 tests, tous verts. - Le ticket que tu as créé pour signaler la duplication de la règle de chevauchement.
- Le guide d'utilisation modifié.
Voir la réponseMasquer la réponse de l'exercice 2
- Analyser et corriger un dysfonctionnement : le bug a été reproduit par un test avant d'être corrigé.
- Recueillir, analyser et mettre à jour les informations sur une version : la nouvelle version est numérotée et décrite.
- Élaborer et réaliser les tests des éléments mis à jour : le nouveau test passe et rien n'a régressé.
- Évaluer la qualité : tu as repéré un défaut de conception et tu l'as tracé.
- Mettre à jour des documentations technique et d'utilisation.
Tu avais trouvé ?
Exercice 3 sur 3 Répondre à la commission Réussi À revoir
Après ta correction du cas type 1, un membre de la commission demande : « Votre test passe, mais comment être sûr qu'il aurait détecté le bug ? »
Que réponds-tu, et que montres-tu ?
Voir la réponseMasquer la réponse de l'exercice 3
Le test a été écrit avant la correction, et il a échoué : c'est ce qui prouve qu'il détecte le bug. Pour le montrer, deux possibilités :
- l'historique Git, si le test a été commité seul avant la correction :
git log --onelinemontre le commit du test, puis celui de la correction ; - ou une démonstration en direct :
git restore --source=HEAD~1 src/Service/Disponibilite.phpremet l'ancienne comparaison, le test passe au rouge sur le cas « créneaux qui se suivent », puisgit restore src/Service/Disponibilite.phprétablit la correction et le test repasse au vert.
Tu peux ajouter que les trois autres cas du fournisseur de données passaient déjà avant la correction : ils prouvent que la correction n'a pas cassé la détection des vrais chevauchements.
Tu avais trouvé ?