devseunggwan/test-suite-speedup

Accélérer une suite de tests lente ou un job CI et le prouver — classer les goulots d'étranglement d'un CI sur plusieurs exécutions, privilégier les correctifs structurels (configuration partagée, fichiers parallèles, étapes de matrice) plutôt que des réécritures fichier par fichier, sonder la sécurité du parallélisme et n'accepter la modification que sur une comparaison CI mesurée avant/après.

Qu'est-ce que test-suite-speedup ?

Cette compétence d'agent IA aide les développeurs à identifier et à corriger les goulots d'étranglement dans les suites de tests lentes ou les jobs CI, en mettant l'accent sur des améliorations structurelles mesurables plutôt que sur des réécritures locales. Elle commence par établir une référence sur la même trajectoire CI, en classant les étapes du CI sur plusieurs exécutions pour identifier les causes structurelles (configuration partagée répétée, sérialisation de fichiers, jobs CI surchargés). L'agent évalue ensuite les gains potentiels, classe les fichiers par phase de lenteur (setup, call, teardown) et classifie chaque fichier lent à l'aide de références détaillées pour les frameworks comme pytest et vitest. La compétence préconise des correctifs structurels dans un ordre précis : d'abord éliminer la configuration redondante dans les fichiers chauds, puis paralléliser les tests, ensuite diviser les jobs CI en étapes de matrice. Chaque modification est validée par une comparaison CI avant/après, garantissant une amélioration mesurable. Elle comprend des scripts pour mesurer les durées des legs CI, détecter les appels redondants et profiler les dépendances. La compétence est conçue pour être utilisée avec des agents d'IA dans des environnements de développement comme Claude Code, Cursor ou Codex, et s'appuie sur divers référentiels et scripts pour guider l'utilisateur tout au long du processus d'optimisation.

Compatible avec~Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/devseunggwan/praxis/tree/HEAD/skills/test-suite-speedup

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait test-suite-speedup ?

Aperçu

Une suite lente tente de faire deux mauvais choix : réécrire manuellement le fichier qui semble le plus lent, et rapporter un chronomètre local comme gain. Les deux passent à côté. Le temps se situe généralement à quelques endroits structurels — une configuration lourde répétée, des fichiers exécutés en série qui pourraient l'être en parallèle, un job CI qui fait le travail de deux jobs — et une mesure locale prise pendant que quelque chose d'autre s'exécute n'est que du bruit. Cette compétence classe d'abord le coût sur le CI, choisit le levier qui déplace le plus de fichiers, et traite un tableau CI avant/après comme le test d'acceptation.

Principe fondamental : le livrable est une comparaison CI mesurée sur la même trajectoire CI, pas un chronométrage local ni des vérifications vertes.

Table de référence — charger uniquement lorsque l'étape s'exécute :

FichierContient
references/ci-measurement.mdClassement des étapes sur plusieurs exécutions, classement par fichier à partir d'un journal pytest -v, bruit du type d'exécuteur, rapport avant/après, rafraîchissement des tranches de durée
references/pytest-slow-test-causes.mdClassification d'un fichier pytest lent par phase et signature ; sleep échappés ; attentes de code produit inaccessibles ; correctifs hermétiques à l'échelle de la suite (clients cloud, temps d'inactivité des bibliothèques) avec tests de garde ; profilage d'un flux de travail à un seul processus ; réduction d'un create_all d'exécution
references/pytest-shared-setup.mdDimensionnement de la partie partageable d'une factory chaude à partir de ses appelants ; partage sécurisé d'un objet pytest coûteux : preuves avant et après, câblage à changement minimal, interaction de sélection de modification
references/vitest-isolation.mdSuites JS/vitest : classification fichiers d'attente vs calcul, fichiers lents uniquement sur CI, coût dominé par les importations, profilage des importations par module, pré-regroupement des bibliothèques en baril, pools isolés vs partagés, sondes de fuite, vérifications de brassage
references/parallel-runner.mdSonde de sécurité parallèle, partition série, règles d'implémentation de l'exécuteur, division de matrice CI
references/ci-redundant-runs.mdExécutions qui ne devraient pas avoir lieu : classification de l'historique des fusions/pushs par arbre, réutilisation du résultat d'un arbre identique, remplacement par une exécution plus récente réelle (API Actions, pas d'analyse de message), concurrence par job, test des portes if: de workflow comme du code, maintien des marqueurs de saut hors du texte de commit
scripts/ci_leg_series.shSecondes par étape de test pour chaque push depuis une date (TSV) — la série pour localiser les changements d'étape ; exécuter en arrière-plan
scripts/ci_durations_diff.pyPour les paires d'exécutions avant/après autour de chaque changement d'étape : comptages collectés, résumés pytest, nouvelles entrées --durations ou entrées agrandies
scripts/boto_call_counter.pyPlugin pytest comptant les appels botocore réels par test — à exécuter lorsque cProfile montre des attentes SSL/socket sous botocore

Quand l'utiliser

  • Une suite de tests, ou le job de test d'un workflow CI, est trop lent et l'utilisateur souhaite l'accélérer.
  • L'utilisateur demande « qu'est-ce qui est encore lent ? » après une accélération antérieure.
  • Pas pour le nettoyage de copie/dédoublement sans objectif de temps — restructurer les tests pour leur réutilisation n'est pas une accélération (voir test-suite-consolidation).

Processus

Étape 1 : Référence sur la même trajectoire CI

Prenez la référence du CI, jamais d'une exécution locale. La référence doit exercer la même trajectoire CI que l'exécution après : lorsque la base de la PR sélectionne le chemin (par exemple prod exécute la suite complète divisée en étapes de matrice tandis que dev exécute une étape avec sélection de modification), prenez la dernière exécution réussie d'une PR vers la même base, et vérifiez que les comptages de réussite/échec/désélectionnés correspondent avant de comparer les temps. Extrayez les temps par étape pour cette exécution, divisez la grande étape par les bannières de section de l'exécuteur, et lisez les commentaires du fichier workflow — les mainteneurs ont souvent déjà écrit le correctif prévu. Classez ensuite les étapes sur plusieurs exécutions, pas une seule, et vérifiez le nombre réel de vCPU de l'exécuteur par rapport au nombre de workers configuré. Commandes → references/ci-measurement.md.

Avant de proposer quoi que ce soit, recherchez les issues et PRs pour un problème d'accélération existant, y compris les issues fermées et les PRs d'expérimentation jetables (gh pr list --state all --search "xdist", --search "test_durations") : ils enregistrent les alternatives mesurées et rejetées, et les conditions dans lesquelles elles ont été rejetées — un levier réfuté lorsque le coût de configuration dominait peut rapporter une fois que les tests liés au CPU le font.

Fait lorsque : un ID d'exécution de référence sur la trajectoire CI correspondante est nommé, ses comptages de test sont enregistrés, et chaque étape de quelques secondes a une médiane et un p90 sur plusieurs exécutions.

Étape 2 : Dimensionner le gain attendu avant de choisir un levier

Gain ≈ (part du temps de la suite couverte par le lot) × (économie par fichier mesurée sur ce sous-ensemble). Un lot qui représente 2 % de la suite donne environ 1 à 2 % même à −60 % par fichier — en dessous du bruit du CI, et l'utilisateur lira « à peine bougé ». Privilégiez les leviers qui déplacent des centaines de fichiers à la fois (un helper de test partagé, un classifieur de pool, le type d'exécuteur) et présentez les migrations fichier par fichier comme la longue traîne. Dites le gain attendu dès le départ.

Fait lorsque : chaque levier candidat a une estimation écrite avec ses entrées, et les estimations en dessous du bruit du CI sont étiquetées comme telles.

Étape 3 : Classer les fichiers et classer chaque fichier principal

Classez les fichiers dans toute la suite à partir d'une exécution CI complète qui enregistre pytest -v (une exécution avec sélection de modification ne couvre pas tous les tests). Validez le classement sur une exécution avec une réponse connue d'abord — l'exécution avant correction doit classer le fichier que vous avez corrigé en premier. Classez ensuite chaque fichier principal par phase (setup / call / teardown) et forme avant de proposer un correctif :

Les chronométrages locaux par fichier sont uniquement pour la forme : exécutez chaque fichier en arrière-plan, regardez pourquoi il est lent (un processus généré par cas, des sleep réels — un sleep dans la chaîne d'entrée d'un test n'est pas un délai), et ne rapportez jamais un nombre pris pendant qu'une autre suite s'exécute. Enregistrez le CPU (user+sys) à côté du temps mur pour chaque fichier. Un fichier qui utilise peu de CPU pour son temps mur attend en temps réel (limiteurs, backoff, polling), et le correctif est un temps fictif du côté du test ; plus de workers ou des réductions d'importation ne l'aideront pas. Un fichier lent sur CI mais rapide localement avec le nombre de workers et l'environnement du CI a besoin d'une reproduction sur le système d'exploitation et la version majeure du runtime du CI (un conteneur node:<major> limité au vCPU du CI), pas d'une supposition ; lisez les lignes de test lent que le journal CI affiche déjà sous ce fichier en premier → references/vitest-isolation.md. Pour pytest, comparez le rapport CI/local par fichier avec le rapport habituel de la suite. Un fichier plusieurs fois au-dessus a un coût uniquement CI, mais sa cause est une hypothèse jusqu'à ce que la propre exécution CI de la PR déplace ce fichier : un A/B de conteneur Linux (par exemple synchronous=OFF dans sqlite réduisant les simulations de planificateur d'environ 35 %) ne s'est pas reporté sur l'exécuteur GitHub, où les mêmes tests ont conservé leurs --durations. Livrez un tel levier seulement avec une cible mesurable dans la première exécution de la PR → references/pytest-slow-test-causes.md.

La cause d'une suite lente est généralement une combinaison : workers sérialisés, fichiers en attente et importations lourdes par fichier. Classez les leviers dans un tableau : cause · preuve (mesurée vs estimée) · secondes CI · correctif · économie attendue.

Une cause devinée à partir des noms --durations (« probablement réanalyse le DAG ») est une hypothèse jusqu'à ce qu'une exécution cProfile de 2 à 3 tests lents le montre ; profilez avant que la cause n'entre dans un plan ou une issue, et retirez-la dans le rapport lorsque le profil dit le contraire.

Fait lorsque : chaque fichier le mieux classé a une cause provenant du tableau de classification, ou est explicitement marqué « nécessite un examen ».

Étape 4 : Choisir le correctif structurel, dans cet ordre

  1. Supprimez la configuration coûteuse répétée dans un fichier chaud (un test qui reconstruit le même objet lourd par appel — un sac DAG, une application, un schéma). Réutilisez la fixture de session existante du dépôt avant d'inventer un cache, et prouvez d'abord que le partage est sûr, puis prouvez que les copies de base et de branche du fichier échouent sur les mêmes mutants de production. Un cache de module/session ne déduplique qu'à l'intérieur d'un seul processus pytest : sur un CI partitionné, vérifiez dans quelle étape chaque consommateur s'exécute avant de compter l'économie. Lorsqu'un test séparé ne fait que relire le résultat coûteux d'un cas paramétré, intégrez ses assertions dans ce cas au lieu de mettre en cache (indépendant des shards), et cassez une assertion déplacée pour prouver qu'elle s'exécute toujours → references/pytest-shared-setup.md.
  2. Exécutez les fichiers de test indépendants en parallèle à l'intérieur du point d'entrée CI.
  3. Divisez le job CI en étapes de matrice pour que les moitiés s'exécutent côte à côte.
  4. Ce n'est qu'alors que vous réécrivez les générations de processus par cas des fichiers les plus lents — changement important, gain modeste. Proposez-le ; ne le faites pas par défaut.

Lorsque la majeure partie du coût provient d'exécutions entières qui ne sont pas nécessaires (backmerges de bot, un arbre identique déjà testé sur une autre branche, des pushs sur le point d'être dépassés), ce levier vient avant tout ce qui précède : classez l'historique des exécutions par arbre et sautez ou réutilisez des exécutions entières → references/ci-redundant-runs.md.

Fait lorsque : le levier choisi est nommé avec son estimation de l'étape 2, et tout levier d'ordre supérieur sauté a une raison d'une ligne.

Étape 5 : Prouver la sécurité du parallélisme et implémenter le changement d'exécuteur

Exécutez chaque fichier en parallèle sous un répertoire d'état jetable, chacun vers son propre fichier de sortie et de code de sortie, listez les échecs, et réexécutez chaque échec seul pour séparer un bug de concurrence de l'environnement. Partitionnez les fichiers qui modifient l'arbre de travail en un groupe série basé sur un marqueur qu'ils portent déjà. Implémentez ensuite l'exécuteur et, si choisi, la division de matrice CI. Procédure et règles → references/parallel-runner.md.

Fait lorsque : chaque échec de sonde parallèle est classifié (concurrence / environnement / préexistant), le groupe série est dérivé d'une recherche plutôt que d'une liste manuelle, et les vérifications de statut requises ont été vérifiées avant tout changement de nom de job.

Étape 6 : Vérifier sur le CI, pas en restant sur les exécutions locales

Respectez les directives de codage du dépôt pour le code de test modifié comme pour le code produit — pas de wrappers passe-plat, déduplication extraite, une preuve de mutation pour tout nouvel helper. Exécutez localement uniquement les fichiers touchés plus le lint, poussez, et regardez les vérifications de la PR en arrière-plan au lieu de bloquer le terminal. Lisez les fils de discussion de la PR avant d'écrire toute note de vérification : les réviseurs bots postent dans les minutes suivant un push.

Lorsqu'un test échoue uniquement dans un environnement local (un venv de dépendance optionnelle, un service factice local), exécutez ce test sur la base non modifiée dans un worktree détaché jetable avant de toucher à la modification : le même échec là-bas est environnement, pas régression. Nommez-le dans le pied de page Not-tested: du commit et dans le corps de la PR, et fermez-le à partir du job CI qui installe cette dépendance (son journal brut : la ligne de résumé plus grep -c FAILED = 0), pas à partir de l'exécution locale.

Une comparaison locale de suite complète base-vs-branche soutient « pas de nouveaux échecs » uniquement dans la mesure où elle a été reproduite : lorsque les échecs uniquement sur la branche partagent une erreur d'environnement, dites « N échecs uniquement sur la branche, tous ; reproduit sur la base » et laissez l'exécution CI fermer le reste — « 0 nouveaux échecs » exagère et doit être corrigé sur chaque surface où il apparaît.

Avant de déclarer un stub à l'échelle de la suite sûr pour la fusion, énumérez comment il pourrait changer les résultats, chacun avec une sonde : (1) les liaisons au niveau du module qui le contournent (git grep -nE "from <mod> import <fn>" — un nom lié lors de l'importation conserve la vraie fonction) ; (2) les appelants de production qui n'attrapent pas l'exception du stub, et si des tests les exécutent sans correctif (les comptages d'appels par test de l'exécution avant le montrent) ; (3) les tests qui affirment le chemin réel — ils doivent se corriger eux-mêmes, imbriqués dans le stub de session ; (4) une mise à niveau de bibliothèque en vol qui déplace le nom corrigé (voir la règle tolérante à la version dans pytest-slow-test-causes.md). Rapportez chacun comme vérifié ou comme une inférence avec ce qui le confirmerait.

Les réviseurs bots peuvent s'exécuter plus longtemps qu'un court sondage : le connecteur Codex marque une PR 👀 pendant la révision et la remplace par 👍 lorsqu'elle se termine sans résultats, parfois après 20 minutes. Limitez l'attente, puis relisez les réactions et les fils de discussion une fois avant le briefing de fusion au lieu de prolonger le sondage. Pour savoir si un 👍 en place appartient au dernier push, lisez son horodatage (gh api graphql → pullRequest{reactions(first:20){nodes{content createdAt user{login}}}}) par rapport à l'heure du push — un 👍 plus ancien que le push est le verdict du tour précédent.

Une exécution pull_request teste la fusion de la tête dans la pointe de base à ce moment-là, pas le propre commit de base de la PR : lisez le sha de base à partir du journal du job (HEAD is now at <m> Merge <head> into <base>) et comparez-le avec l'exécution push de ce commit de base.

La métrique par fichier est la mesure après lorsque la suite a grandi entre la référence et l'exécution de la PR : les secondes de l'étape entière mélangent alors les tests d'autres commits (comparez d'abord les comptages collected N items). Rapportez les secondes CI du fichier touché des deux exécutions comme résultat et les temps d'étape comme contexte. Après avoir fusionné la base dans la PR, reconstruisez la bande de comparaison à partir d'autres exécutions de PR dans la même période, pas la bande précédente : la propre suite de la base continue de croître, donc l'ancienne bande peut faire ressembler un vrai gain à une régression. Dites dans le rapport quand la bande elle-même a bougé.

Fait lorsque : une exécution CI de la branche sur la même trajectoire que la référence est terminée, et chaque résultat de révision ouvert est répondu.

Étape 7 : Rapport avant/après

Un tableau avec les deux ID d'exécution : temps mur du job (maintenant = l'étape la plus lente), l'étape auparavant lente et la moitié inchangée, plus la ligne de résumé des tests par étape. Pour un gain réparti sur plusieurs PR, un tableau : référence → chaque étape fusionnée → maintenant, avec la part que chaque PR a contribuée. Nommez la trajectoire CI que la PR n'a pas exercée comme non vérifiée, dites qu'il s'agit d'une exécution de chaque (variance de l'exécuteur non mesurable). Les gains espérés mais jamais tentés (par exemple, un changement de type de worker connu pour économiser 4 minutes mais nécessitant une réorganisation des workflows) sont en bas du tableau, pas ailleurs — les PRs précédentes les ont notés ; l'équipe de maintenance les lira une fois par levier.

Fait lorsque : le tableau est poussé dans un commentaire de PR et que le propriétaire de la PR a confirmé que le temps de l'étape est acceptable.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills associés