Recette (campagne d'acceptation)¶
La recette est la revue d'acceptation du logiciel écran par écran et feature par feature, menée avant une livraison : on vérifie que tout est conforme à la doc publiée et que les parcours sont fluides pour un utilisateur naturaliste. C'est un chantier à part entière : elle s'ouvre sur un EPIC, se déroule en sessions, et se clôt par les passes du cycle. Sa particularité : chaque remarque finit en issue, et chaque correction bloquante revient avec son test, si bien que la campagne laisse derrière elle un filet qui la rejoue.
Où vit la campagne : l'EPIC est le tableau de bord
Le déroulé vivant (sessions cochées, constats, comptes-rendus, task-list des issues) vit dans
l'EPIC de recette sur GitHub (label recette). Les comptes-rendus de session y sont postés
en commentaires : GitHub est la mémoire de la campagne ; cette page en est la méthode.
Campagne courante :
#1363.
Le déroulé d'une campagne¶
L'ordre des sessions suit le parcours métier réel (importer, puis vérifier, puis déposer, puis valider). Règle d'unicité : un écran est déroulé en entier dans une seule session propriétaire ; ailleurs il n'est qu'écran de transit (seule la fluidité de la transition est notée). La base de recette vit d'une session à l'autre.
Ce tableau est le seul inventaire des sessions, état compris. Il ne se recopie nulle part, pas
même plus bas dans cette page : c'est ainsi que « les huit sessions existent » a survécu à l'arrivée
de la neuvième et de la dixième (#3885). Un test le confronte au dossier dans les deux sens
(InventaireDesSessionsTest).
| # | Session (~1 h) | État | Écrans propriétaires |
|---|---|---|---|
| S1 | Premier contact (base vierge, connexion, déclarer un site) | jouée | accueil (dont le bandeau de compteurs vivant, #1376), Connexion, Mes sites, carré, Point |
| S2 | Importer une nuit (carte SD vers le pivot) | jouée | importation (+ cas dégradés), Rattachement, passage, diagnostic |
| S3 | Vérifier (pré-check + écoute + verdict) | à rejouer au delta | qualification (+ raccourcis clavier) |
| S4 | Déposer et suivre (lot, dépôt réel, suivi) | écrite, à jouer | lot |
| S5 | Valider (sur une nuit déposée) | partielle | Sons & validation (parcours expert #2790), fiche espèce |
| S6 | Exploiter et piloter (vues agrégées) | partielle | Activité de la nuit, puis Carte & passages, Espèces & observations, Audit, recherche |
| S7 | Réglages, interrupteurs OFF, états dégradés | écrite, à jouer | tous les onglets, contribués par les features, + chrome |
| S8 | Récupérer une nuit déposée (P12 de bout en bout, carte SD réelle) | écrite, à jouer | Connexion, Mes sites, Toutes mes nuits, passage |
| S9 | Installer et mettre à jour (winget, machine Windows réelle) | écrite, à jouer (#3621) | l'installation et la montée se jouent hors de l'application ; une fois installée, la session traverse quand même l'accueil (S9-04), le bandeau d'annonce de mise à jour (S9-14) et « À propos » (S9-11) |
| S10 | Le poste Windows (verrou du dossier de travail, couleur en console) | écrite, à jouer | aucun : comportements de plateforme |
| S11 | La commande sur un poste Linux (.deb, Flatpak, AppImage) |
écrite, à jouer (#4071) | aucun : ce qui se joue est l'exposition de la commande et l'entrée de menu, hors de l'application |
« Jouée » se dit de la session telle qu'elle était. S1 porte huit cases écrites après sa dernière exécution - S1-30 à S1-33 (#3458) et S1-34 à S1-37 (#3806), dont une perceptive : elles n'ont jamais été exercées à la main. Une session ne se re-marque pas « à jouer » pour autant, mais la campagne suivante doit savoir par où reprendre, faute de quoi une capacité passe pour vérifiée par un script qui ne l'a pas couverte.
Chaque session se joue en trois temps : une passe statique en solo (préparation, postée en commentaire), la session interactive (pilotage écran par écran), puis un triage à froid.
D'où viennent les cases : la passe 6 d'un chantier¶
Un script ne se remplit pas au moment de jouer la campagne : il se remplit à la clôture de chaque chantier, en passe 6. La règle y est posée ainsi :
Une capacité ajoutée par le chantier n'est pas finie tant qu'on ne sait pas comment la vérifier à la main, et que ce « comment » n'est pas écrit là où on le retrouvera.
Une case est terminée quand elle est rejouable par quelqu'un qui n'a pas fait le chantier, autant de fois que nécessaire. Trois pièces, et il en manque une seule pour qu'elle redevienne une intention : le geste, l'observation attendue, et la fixture. C'est pour cette dernière que les cartes SD sont générées depuis une spec plutôt que stockées : une donnée fabriquée à la main pour une campagne ne revient pas à la suivante. Quand le cas n'est porté par aucune fixture, étendre la spec du générateur fait partie de la passe 6.
Les scripts de session¶
Chaque session a son script figé, versionné sous recette/sessions/. Un script est à la fois la
checklist rejouable (une case = un fait observable, groupée par étape) et le relevé de la dernière
passe (verdict par axe, issues produites, renvois, notes de méthode). Passer d'une campagne à l'autre,
c'est re-dérouler la checklist ; ses annotations disent ce qui avait été trouvé la fois d'avant.
Gabarit d'un script : en-tête (écrans propriétaires · features · statut) → objectif → environnement →
[raccourcis] → le script (points numérotés Sxx-NN, groupés par étape) → verdict par axe →
issues produites → renvois et décisions → notes de méthode.
Trois états, et « couvert » reste réservé à ce que la CI prouve (#3764)¶
Une case est dans l'une de trois situations. Les réduire à deux fabrique un vert creux :
| État | Qui tranche | Comment il se déclare |
|---|---|---|
| Asserté | la CI, et elle rougit quand le logiciel a tort | un test porte @CasDeRecette(value = "S1-02", portee = A_L_ECRAN) |
| Perceptif | un humain, en regardant | la case porte la marque *perceptif*, posée en passe 6 |
| Non couvert | personne | ni l'une, ni l'autre |
Le deuxième état n'est pas une couverture, et c'est tout l'enjeu. Un scénario qui joue un cas
perceptif le cite comme n'importe quel test - c'est le seul lien vers le script - si bien que sans
distinction il gonflerait le compte des couverts d'un cas que personne n'a regardé. Un tel
scénario se déclare donc @CasDeRecette(value = "S1-26", jugement = HUMAIN, portee = A_L_ECRAN).
Où se lit le verdict (#4142)¶
Tout cas cité déclare aussi sa portée, et l'attribut n'a pas de valeur par défaut : c'est le compilateur qui pose la question, à l'écriture du test, quand celui qui écrit sait encore ce que son scénario truque.
| Portée | Ce que le clip prouve | Ce qu'elle exige |
|---|---|---|
A_L_ECRAN |
ce que le cas demande de constater est ce que la caméra enregistre | rien de plus |
HORS_APPLICATION |
le geste côté produit, et lui seul | une réserve, disant ce que le clip ne prouve pas |
Le second cas est celui des sessions dont l'objet est ailleurs : le dépôt reçu par Vigie-Chiro, la carte SD réelle, la nuit rapatriée du serveur, l'installeur sur un vrai poste, la commande dans un vrai terminal. Un clip bouchonné y montre un écran convaincant et creux - il ne devient pas faux, il devient muet sur son propre objet, ce qui est pire parce qu'on le regarde en croyant savoir.
CorrespondanceRecetteTest garde les deux sens : la réserve est exigée si et seulement si la portée est
HORS_APPLICATION, et elle doit figurer sur la page du clip. Une réserve qui ne vit que dans le code
ne borne rien : personne ne lit une annotation en regardant une vidéo.
Un seul cas cité porte aujourd'hui HORS_APPLICATION : S1-04, « ouvrir le site sur la
plateforme », dont le geste se termine dans un navigateur que le clip ne peut pas montrer. Sa réserve le
dit, sur la page du clip. Le reste est à l'écran, et ce n'est pas un hasard : ce qui a été couvert
jusqu'ici est précisément ce qui se filme sans réserve. Les quelque 220 cas dont le verdict est hors de
l'application restent devant nous.
Cette page a affirmé pendant un temps que tous les cas étaient à l'écran, alors que S1-04 ne l'était déjà plus. Une phrase de ce genre ne rougit nulle part : c'est pourquoi le compte vient de la machine, et pourquoi la phrase qui l'entoure doit se relire à chaque clôture.
Le compte n'est pas recopié ici : CorrespondanceRecetteTest l'imprime à chaque lancement, sous la forme
« Portée des cas cités : N à l'écran, M hors application ». Un nombre écrit à la main dans une page dérive
sans que rien ne le signale, et c'est exactement ce qui a produit #3885.
Les deux sources se tiennent l'une l'autre. Le script dit ce qu'un cas demande, le code dit ce
qu'un test prouve, et CorrespondanceRecetteTest les confronte : il rougit sur un cas marqué
perceptif que du code prétend asserter, comme sur un scénario qui se déclare humain là où le script
n'a rien marqué. Sans ce recoupement, la marque dériverait comme la prose avait dérivé avant #3728 -
le script disait « perceptifs » en toutes lettres, et aucune machine ne le lisait.
Les sessions, leur état et le lien vers leur script sont dans le tableau en tête de page - sans les compter ici, puisque c'est un compte recopié qui a produit #3885. S'y ajoutent les passes ciblées, qui ne sont pas des sessions et ne figurent donc pas au tableau :
- Passe ciblée · constats en attente : les huit constats qu'une image fixe ne peut pas juger, tranchés avant d'ouvrir les chantiers voisins (jouée le 2026-08-07).
- Passe de coutures · stabilisation : les correctifs de la stabilisation tiennent-ils ensemble ? Un parcours qui traverse quatre écrans, là où chaque session n'en déroule qu'un. Condition d'entrée de la campagne 2 (à jouer).
Le clip : ce qu'un cas perceptif donne à regarder (#3667)¶
Un cas perceptif n'était jusqu'ici jugeable qu'en refaisant les manipulations à la main. Il l'est maintenant en regardant un extrait.
Une séance filmée produit, à côté du film, un clip par test et un index.md qui se lit par
cas : on saute à l'extrait plutôt que de regarder trente minutes.
.github/scripts/lance-test-filme.sh ConnexionModaleViewTest # une classe
.github/scripts/lance-test-filme.sh --planche # tout ce qui cite un cas
L'index dit par quel moyen chaque cas s'audite, et c'est ce qui l'empêche d'inviter à cocher n'importe quoi : « en regardant » quand quelque chose a paru à l'écran, « en lisant le test » quand rien n'a paru. Un test de ViewModel cite des cas et n'ouvre aucune fenêtre - son clip est noir, et c'est le résultat juste.
Le mode d'emploi et les pièges du banc vivent dans CI/CD.
Le clip ne rend pas le verdict, il le rend possible. Filmer une application cassée produit une vidéo parfaitement valide : ce qui fait foi reste l'assertion pour un cas asserté, et le regard d'un humain pour un cas perceptif.
La fiche d'évaluation : six axes¶
Chaque écran est noté sur six axes, verdict trivalué (OK / remarque / bloquant). Les axes P et D s'instruisent en passe statique (ils ne demandent pas de piloter l'écran) ; la session interactive ne sert qu'à C, E, F, R.
| Axe | Ce qu'on vérifie |
|---|---|
| C · Conformité | chaque affirmation de docs/ecrans/<ecran>.md est vraie à l'écran |
| E · États | vide / occupé / erreur / désactivé expliqué (IndicateurBlocage + tooltip) |
| F · Fluidité | état visible, annulation possible, erreur prévenue, pas de mémorisation exigée |
| R · Clavier | raccourcis documentés opérants, tab / focus cohérents |
| P · Parité CLI | capacités métier de l'écran couvertes par une commande |
| D · Doc & captures | fiche à jour, captures du manifeste = état réellement livré |
Une vérification = un fait vérifiable
En séance, chaque point est un seul fait (S4-01, S4-02…), jamais un contrôle groupé, et les
questions se posent une par une. C'est ce qui rend un constat traçable jusqu'à son test.
Ce qui est bloquant¶
Un constat est bloquant livraison si :
- (a) il contredit la doc publiée ;
- (b) il fait courir un risque de perte ou corruption de données ;
- (c) il crée une impasse de parcours sans échappatoire ;
- (d) une désactivation est muette sur le chemin nominal ;
- (e) une friction ergonomique forte touche le parcours nominal (geste contre-intuitif, libellé trompeur), au jugé pendant le triage.
Tout le reste part en « v2 produit » par défaut.
Le triage de fin de session (trois bacs)¶
- Bloquant livraison (critères a-e) : issue de finalisation immédiate, label
recette, ajoutée à la task-list de l'EPIC. Voix première personne, closing keywords anglais. - v2 produit : issue au milestone « v2 produit », non reliée à l'EPIC. Le parapluie ergonomique est #786 ; tout constat ergonomique lui est d'abord confronté.
- Non retenu / déjà tracé : simple renvoi dans le compte-rendu de session.
La boucle : un constat devient un test¶
C'est le cœur de la recette côté dév, et sa différence avec une simple checklist. Un constat bloquant ne se contente pas d'être corrigé : il revient avec son test, pour ne plus jamais régresser en silence. Selon l'axe, la couche de cristallisation diffère :
| Axe | Où le constat se cristallise |
|---|---|
| C, E | test d'intégration de vue (*VueIntegrationTest) ou parcours E2E (Parcours*E2ETest) |
| R | test TestFX qui rejoue raccourcis et focus |
| P | test de commande CLI en golden : voir CLI |
| D | harnais de captures (ApercuFx / Capture*) + approbation |
| F | irréductiblement humain pour la part perçue - irréductible à une assertion, non à un dispositif : le geste se filme et se regarde (#3667). Les invariants objectivables (annulable, désactivation expliquée) rejoignent C / E |
Le mécanisme s'appuie sur des patrons déjà en place : le cliquet (CliquetFixturePassageTest), qui
empêche une dette de fixtures de repousser, et « la doc est tenue par un test »,
qui empêche la doc de dériver. La recette est donc une fabrique de tests : la campagne se termine, le
filet reste.
En pratique : trois constats, trois filets à trois étages
La campagne courante l'a déjà fait plusieurs fois, et chaque fois le test atterrit dans la couche qui convient :
- Un raccourci clavier muet (axe R). En S3, « Espace » n'ouvrait pas la lecture sur l'écran de
qualification, et rien ne le testait (#1504). La correction est revenue avec son test TestFX :
QualificationViewTestvérifie désormais qu'« Espace est capté pour la lecture avant le nœud focalisé », donc n'active plus par mégarde un bouton de verdict. - Une régression silencieuse de données (critère bloquant b). Toujours en S3, une nuit déposée
pouvait régresser vers « Vérifié » depuis l'écran de vérification (#1514) : un verdict figé qui
se défige, c'est un risque d'incohérence. Le filet est posé au bon étage, côté service :
ServiceQualificationTestgarantit qu'« une nuit déposée refuse tout nouveau verdict ». - Un constat qui devient un patron (axe R, transverse). « Échap » ne fermait aucune modale
(#1505). Plutôt qu'un correctif écran par écran, la correction a extrait un patron commun
(
Modales.fermerParEchap) gardé parModalesTest: un constat de recette a nourri l'harmonisation.
Rejouer une campagne de façon déterministe¶
Pour revalider aux jalons suivants sans tout re-piloter à la main, on rejoue le fond fonctionnel headless :
- La CLI est le moteur de rejeu.
fr.univ_amu.iut.cliexpose le métier en commandes scriptables (importer,qualifier,deposer,auditer,reactiver…). Un scénario qui enchaîne des commandes contre une fixture et compare la sortie à un golden rejoue un parcours entier sans IHM. Voir CLI. - Les parcours E2E (
Parcours*E2ETest) sont les scripts de départ des sessions : ils pilotent les vrais ViewModels et services sur base jetable. - Les fixtures (cartes SD de recette, workspace) sont générées depuis une spec de quelques
kilo-octets plutôt que stockées en binaire : versionnables, rejouables à l'identique. Les 10 cartes de
recette (+
sd-nominale.zip) se reconstruisent à l'identique via le générateur déterministe : voir Fixtures (générateur de cartes SD).
Jamais de secret dans le dépôt
Le connexion.json d'un workspace de recette contient un token Vigie-Chiro : il n'est jamais
versionné. Un rejeu qui doit écrire sur la plateforme reçoit son jeton par variable
d'environnement ou secret CI, pas depuis un fichier committé.
Où ça vit¶
dev-docs/recette/index.md: cette page, la méthode.-
dev-docs/recette/sessions/: les scripts de session figés, plus les passes ciblées. Leur liste et leur état sont dans le tableau de la section « Le déroulé d'une campagne », en tête de cette page, et nulle part ailleurs.Cet inventaire est la seule source. Il a été recopié ailleurs - dans le cycle de chantier et dans
CONTRIBUTING.md- et les trois copies ont divergé en quelques heures : S7 a été écrite (#3517) le jour même où les deux autres affirmaient qu'elle n'existait pas. Les renvois pointent désormais ici plutôt que de répéter la liste.Et cette page s'était recopiée elle-même : ce paragraphe énumérait les sessions et leur état, en s'arrêtant à huit alors que le tableau en portait dix. Un avertissement qui ne surveille que l'extérieur ne voit pas le doublon qu'il abrite. La liste a donc été supprimée d'ici plutôt que corrigée - on ne garde pas un doublon, on le retire (#3885). -
recette/fixtures/spec/: les specs (YAML) des cartes SD, matérialisées par le générateur déterministe ; voir Fixtures (générateur de cartes SD). - L'EPIC (labelrecette) : le déroulé vivant, les comptes-rendus, la task-list des issues.