Filmer la recette depuis le graphe de scène¶
Prototype de l'extension JUnit 5 évoquée en discussion, à comparer à lance-test-filme.sh sur une
classe réelle. Sept classes, 589 lignes hors commentaires contre 944 de bash, mais ce n'est pas le
volume qui compte : c'est que quatre familles de défauts n'ont plus où se produire.
Le renversement¶
Le script filme le bureau X. De dehors, « une fenêtre est-elle à l'écran » se devine par la luminance moyenne de l'image, et « quand ce test a-t-il joué » se calcule en recalant l'horloge du journal sur celle du film. Les deux heuristiques marchent, elles sont bien gardées, et elles ont coûté #3696, #3707, #3774, #3788, #3835 et #4053.
Ici, CameraDeScene photographie le graphe de scène par Scene.snapshot, et l'extension borne
l'enregistrement sur l'exécution du test. Les deux questions cessent d'être des mesures : ce sont
des faits que la JVM connaît.
Ce que cela retire du dispositif¶
| Dans le script | Devient |
|---|---|
| Seuil de luminance à 20, recalibré à chaque changement de gestionnaire de fenêtres | Window.getWindows() : une fenêtre est visible ou non |
t0 = arrêt - durée, l'instant relevé avant wait, le tube nommé et le printf q >&3 |
Un fichier par test : plus qu'une horloge, donc plus de recalage |
couper_par_luminance, couverture_des_plages, plages_du_journal, reperes.tsv |
Sans objet |
carton_de_titre : drawtext, fold -s -w 54, résolution DejaVu, fc-match, concat -c copy, contrôle des dimensions du montage |
CartonDeTitre : Graphics2D, police logique, repli mesuré en pixels, poussé dans le même encodeur |
| Le cas d'auto-test à tesseract | CartonDeTitreTest : l'invariant porte sur des chaînes |
libelle_du_cas en awk + sed |
LibelleDesCas, avec huit cas unitaires |
Les cinq préconditions, verifier_tout, --verifier, la relance sans WAYLAND_DISPLAY |
Rien : on filme dans le mode Monocle headless où les tests tournent déjà |
Xvfb, openbox, xdotool, glass.platform=gtk, testfx.robot=awt, java.awt.headless=false |
Rien de tout cela n'est requis |
Ce qui reste requis, et que le tableau ne dit pas : ffmpeg. Encodeur.VersFfmpeg le lance en
sous-processus et le nourrit en rawvideo, on garde donc libx264 et les réglages du script. Ce spike
supprime la dépendance à un bureau X, pas la dépendance à un encodeur. L'interface Encodeur
est le point de substitution si l'on veut un jour encoder en Java.
En échange, ce qui disparaît n'est pas seulement de la complexité : le banc devient exécutable
sous Windows et macOS, où il ne l'est pas aujourd'hui. La session s10 est précisément celle du
poste Windows.
Ce qui reste du script : --planche garde son sens, mais devient -Dtest=A,B,C sans autre
apparat, puisqu'un fichier par test se produit de lui-même. CorrespondanceRecetteTest reste le
bon endroit pour dériver la liste.
Contenu¶
| Fichier | Rôle |
|---|---|
EnregistreurDeFilm.java |
L'extension JUnit 5. Ne fait rien sans -Drecette.film |
CameraDeScene.java |
AnimationTimer qui compose les fenêtres visibles, CENTRÉES sur la toile |
Enregistrement.java |
Une séance par test : file bornée, fil d'écriture, bilan |
Encodeur.java |
Interface, et l'implémentation qui nourrit le ffmpeg du banc en rawvideo |
CartonDeTitre.java |
Le carton, dessiné au format du film |
LibelleDesCas.java |
Le libellé d'un cas, lu dans les sessions |
CameraDeSceneTest, EncodeurTest |
Les gardes du placement et de la résolution de l'encodeur |
IndexDesCas.java |
L'index par cas, écrit en fin de session JUnit |
CasDeRecette.java |
Rappel de la forme supposée. À supprimer à l'intégration |
Branchement¶
@ExtendWith({ApplicationExtension.class, EnregistreurDeFilm.class})
class ConnexionModaleViewTest { ... }
L'ordre importe : BeforeTestExecutionCallback s'exécute après tous les @BeforeEach, donc après
le démarrage de l'application par TestFX. La caméra trouve une scène montée.
Le profil recette-filmee se réduit alors à une propriété :
<profile>
<id>recette-filmee</id>
<properties>
<recette.film>true</recette.film>
</properties>
</profile>
et le lancement à ./mvnw test -Precette-filmee -Dtest=ConnexionModaleViewTest, sans script,
sans écran, sans gestionnaire de fenêtres.
Propriétés reconnues : recette.film (présence suffit), recette.film.taille (défaut
1280x900), recette.film.dossier (défaut target/recette/clips), recette.sessions (défaut
dev-docs/recette/sessions).
Ce qui est éprouvé, et ce qui ne l'est pas¶
CartonDeTitre et LibelleDesCas ont été compilés et exécutés en headless : 13 tests,
20 assertions, aucun échec, accents compris et sans fontconfig. Les deux moitiés ont été
éprouvées par mutation : neutraliser LibelleDesCas fait rougir 7 tests sur 13, désactiver le
repli de CartonDeTitre en fait rougir 1, et retirer la case à cocher du motif de puce en fait
rougir 1. apercu-carton.png est le rendu réel, à relire à l'œil.
1. Le débit : mesuré, et le compteur ne bouge pas¶
Seize clips produits, dix sur ConnexionModaleViewTest et six sur ParcoursNavigationE2ETest :
zéro image perdue. Le fil FX suit la cadence de 10 images par seconde.
Le surcoût en temps, lui, n'est pas mesurable sur un poste chargé. Trois exécutions de chaque sur la même classe E2E :
| avec film | sans film | |
|---|---|---|
| 1 | 37,49 s | 26,46 s |
| 2 | 28,51 s | 25,13 s |
| 3 | 26,57 s | 38,04 s |
| moyenne | 30,86 s | 29,88 s |
Le troisième run sans film est le plus lent de tous. L'écart des moyennes vaut +0,98 s pour une étendue de 12,91 s : la marge est du même ordre que la variance de la machine. Un seul échantillon aurait donné « +42 % », et c'est ce qu'il a donné avant qu'on répète.
La leçon vaut au-delà de ce spike : comparer la marge à la variance, pas à zéro.
2. La position des fenêtres : c'était un défaut, il est corrigé¶
Le clip d'un parcours montrait « ieChiro Companion » au lieu de « VigieChiro Companion », et « gende » au lieu de « Légende ».
Mesuré au pixel : la scène fait 1100×720 et elle était dessinée à x = -51, perdant ses
51 premiers pixels tandis que 231 pixels de toile restaient vides à droite. Window.getX() situe
la fenêtre sur un écran virtuel Monocle étranger à la toile.
CameraDeScene.decalage centre désormais, sans lire aucune coordonnée. Sur le clip refait, le
contenu occupe x de 90 à 1189 et y de 90 à 810, marges de 90 et 89 pixels, titre entier.
CameraDeSceneTest garde le placement en cinq cas.
Ce qui rendait ce défaut dangereux est sa modestie : un bord amputé de cinquante pixels se lit comme une mise en page, pas comme un défaut. Il n'aurait fait rougir aucun test.
4. L'allocation : le bassin de tampons n'est pas justifié¶
composer() alloue une toile neuve et snapshot un WritableImage neuf à chaque image. La
question posée était : faut-il un bassin de tampons rendus par le scribe après écriture ?
Non, et la mesure le dit. On a poussé la cadence jusqu'à ce que le compteur d'images perdues
bouge, sur ParcoursNavigationE2ETest :
| Cadence | Images écrites | Images perdues |
|---|---|---|
| 10 img/s (nominale) | 212 | 0 |
| 25 img/s | 417 | 0 |
| 50 img/s | 710 | 0 |
| 50 img/s, file étranglée à 1 au lieu de 60 | 703 | 0 |
Le compteur n'est pas mort, il a été éprouvé : en ralentissant le scribe de 120 ms par image, il rapporte 45 pertes. Un zéro non éprouvé n'aurait rien valu.
À cinq fois la cadence nominale, et même avec une file de profondeur 1, rien ne se perd. Le bassin serait donc une optimisation que rien ne justifie, et il coûterait cher en risque : il faudrait que le scribe rende chaque tampon, et un tampon rendu trop tôt réécrirait une image encore en attente.
Le piège reste documenté au-dessus de composer(), parce que la tentation, elle, reviendra.
Ce qui reste à vérifier¶
Le pipeline logiciel. Le rendu passe par Prism SW en headless. Quelques effets et mélanges diffèrent à la marge du rendu matériel. Sans conséquence pour un film de recette, à vérifier une fois si un test perceptif juge au pixel près.
Deux limites assumées : le curseur n'est pas rendu (il se dessine à partir d'un EventFilter sur
MOUSE_MOVED, ce qui permet en échange de surligner le nœud touché), et les boîtes natives
FileChooser restent invisibles. Cette seconde limite ne coûte rien ici, puisque TestFX ne sait pas
les piloter non plus : si tes tests passent aujourd'hui, c'est qu'elles sont déjà derrière un port.