Passer au contenu
UTX Web

En coulisses

Des tests ciblés : ne lancer que ce qui peut casser

Publié le L’équipe UTX Web

Read in English

Notre usine logicielle ouvre beaucoup de demandes de fusion. Des agents d’IA travaillent en parallèle, chacun sur son propre changement, et chaque changement passe par les mêmes vérifications avant d’être fusionné. C’est la règle, et elle ne plie pas.

Le problème, c’était le sens de « les mêmes vérifications » : tout, chaque fois. Une correction d’une ligne dans un rapport lançait toute la suite de tests, les analyses de sécurité, la construction du conteneur et les tests d’isolation. Sur un de nos produits, cela représentait environ 11 minutes de machine de build par demande de fusion, et autant après la fusion. Le coût suivait la taille du code, pas la taille du changement.

Nous avons donc appris au pipeline à se poser une question simple, d’abord : qu’est-ce que ce changement peut casser ?

Première étape : un plan avant le moindre test

Chaque exécution commence maintenant par une petite tâche de planification. Elle n’installe rien et se termine en quelques secondes. Elle regarde les fichiers touchés par le changement et décide quelles tâches tournent, et quels tests elles lancent. Toutes les autres tâches lisent ce plan.

Le plan est un simple script, avec ses propres tests. Nous avons d’abord regardé les outils existants. Nous avons choisi le nôtre parce que nous voulions pouvoir tester le plan lui-même : quand il prend une décision, un test dit pourquoi.

Deuxième étape : suivre les importations

Le script lit chaque fichier de code et de test, et dresse la carte de qui importe quoi. Puis il remonte cette carte à partir des fichiers modifiés. Un test est retenu s’il se trouve à deux importations ou moins d’un changement, ou si le test lui-même a changé.

Deux, pas une. Nous avons commencé avec une seule, et un vrai raté nous a corrigés : un changement atteignait un test en passant par un deuxième fichier, la demande de fusion est passée, et l’exécution complète après la fusion l’a attrapé. La correction n’a pas été une exception ponctuelle. La profondeur est passée à deux, et ce cas précis est devenu un test du sélecteur.

Troisième étape : les dépendances que les importations ne voient pas

Les importations ne disent pas tout. Certains tests lisent des fichiers par leur chemin : migrations de base de données, fichiers de consignes, données de départ, réponses d’API enregistrées, jeux d’essai. Aucune importation ne les relie, donc la carte seule les manquerait.

Pour ceux-là, un petit fichier de règles associe chaque chemin aux tests qui le lisent. Et comme une liste de ce genre vieillit mal, le sélecteur a ses tests de garde. Si un test se met à lire un dossier de jeux d’essai qui ne le sélectionne pas, le build échoue. Si la configuration des tests importe un fichier que le sélecteur ne traite pas comme global, le build échoue. Le filet vérifie ses propres trous.

Quatrième étape : des tâches qui ne tournent que quand elles comptent

Les tests ne sont pas le seul coût. Le plan décide aussi des autres tâches :

  • Les analyses de sécurité tournent sur les fichiers modifiés quand un changement touche la connexion, le chiffrement, l’isolation des clients ou la porte d’entrée Web.
  • La construction et l’analyse du conteneur tournent quand le changement touche le build lui-même : le Dockerfile, les dépendances, les réglages du compilateur.
  • L’audit des dépendances tourne quand les fichiers de paquets changent.
  • Le lint et le formatage ne vérifient que les fichiers modifiés.

Quelques vérifications tournent sur chaque demande de fusion, quoi qu’il arrive : la vérification des types, la suite d’isolation des clients, la recherche de secrets et la tâche de planification elle-même.

La règle qui garde tout ça sûr : dans le doute, tout

La ligne la plus importante du sélecteur, c’est sa règle par défaut. Si un fichier modifié n’est couvert par aucune règle, le plan lance la suite complète. Les fichiers globaux, comme la configuration des tests ou la liste des dépendances, déclenchent aussi une exécution complète après la fusion. Même chose pour toute migration de base de données, une poussée forcée ou le moindre doute sur la dernière exécution réussie.

La vitesse ne s’achète jamais avec une supposition. Quand le plan ne sait pas, il lance tout.

Derrière la demande de fusion : trois autres filets

Cibler les tests d’une demande de fusion n’est sûr que parce que d’autres vérifications se tiennent derrière.

  1. Après une fusion, la base est la dernière exécution verte, pas le commit précédent. Si une exécution antérieure a échoué ou a été remplacée par une plus récente, ses changements sont sélectionnés de nouveau. Rien ne passe entre les mailles parce qu’une exécution a sauté.
  2. Chaque nuit, tout tourne. La suite complète, les seuils de couverture, les évaluations de l’IA et les tests de mutation, qui modifient le code exprès pour vérifier qu’un test s’en aperçoit. L’exécution de nuit ne déploie jamais rien. Elle fait seulement rapport.
  3. Une nuit rouge ouvre un seul billet de suivi. Il nomme le commit, la dernière exécution réussie et l’étape en échec, avec une marche à suivre : relancer le test en échec cinq fois pour distinguer un test instable d’une vraie régression, puis faire une bissection. Si le sélecteur a manqué une dépendance, la correction va dans ses règles, avec un test. Un test instable se corrige, il ne se relance pas jusqu’à ce qu’il passe.

Et rien ne se déploie à partir d’une tâche annulée ou en échec. Le déploiement vérifie explicitement le résultat de chaque tâche.

Ce que ça a changé

Les chiffres honnêtes dépendent de la taille du changement.

  • Les petits changements y gagnent beaucoup. Pour les demandes de fusion qui touchent cinq fichiers ou moins, l’exécution médiane retient moins de 3 % des fichiers de test. Un changement d’un seul fichier que nous avons mesuré a pris environ 2 minutes au lieu de 11.
  • Les gros changements lancent encore la majeure partie de la suite. Une demande de fusion qui regroupe une fonction sur 35 fichiers atteint le cœur partagé, et ce cœur est importé par presque tous les tests. Environ 70 % des tests tournent. C’est correct : un changement aussi large mérite une vérification aussi large.
  • Le cœur décide jusqu’où le ciblage peut aller. Découper les gros fichiers a à peine bougé les chiffres. Ce qui les fait bouger, c’est d’avoir moins de modules « carrefours » que tous les tests importent. Le sélecteur a donc changé notre façon d’écrire le code : des limites de taille par fichier, aucune importation circulaire, et des outils de test qui ne montent que la partie de l’application dont un test a besoin.

Ce que nous avons essayé, puis abandonné

Nous avons aussi construit une carte plus fine, à partir de la couverture ligne par ligne : lancer chaque test une fois, noter les lignes qu’il exécute, cibler par ligne. Elle prenait six minutes à construire et, sur les neuf mêmes demandes de fusion, elle retenait plus de tests que la règle des importations : 901 contre 749. La préparation commune des tests exécute la moitié de l’application pour chaque test de base de données, donc chaque test « touchait » presque tout. Plus précis sur papier, plus large en pratique.

Nous avons aussi essayé plus de processus de test en parallèle sur de petites machines de build. Chaque processus reçoit sa propre copie de la base de données de test, donc les tests ne partagent jamais de lignes. Sur un portable à 4 cœurs, une exécution est passée d’environ 6 minutes à 2. Sur une machine de build à 2 cœurs, le gain était presque nul. Les tests gourmands en calcul et la base de données se disputaient simplement les deux mêmes cœurs.

Pourquoi ça compte pour nos clients

Vous ne verrez jamais ce pipeline. Ce que vous voyez, c’est le résultat : les petites corrections vous arrivent plus vite, parce qu’elles n’attendent pas derrière une suite complète dont elles n’ont pas besoin. Et les gros changements reçoivent quand même le traitement complet, chaque nuit, avant que quiconque déclare la branche principale verte.

Rapide là où c’est sûr. Rigoureux là où ça compte. C’est la même règle que nous appliquons aux logiciels que nous développons pour vous.

Si votre équipe se bat contre des builds lents ou des tests instables, nous pouvons vous aider.

Tous les billets

Vous aimeriez travailler comme ça sur votre propre code ?

Nous aidons les équipes de développement à obtenir des résultats fiables avec les outils d’IA, dans leur propre code.

Voir nos services
Ou appelez-nous
514-791-5584
Ou écrivez-nous
rafal@utxweb.com