Un seul Runner disponible, des jobs en attente et des tests UI qui se terminent toujours au même moment : votre matrice GitHub Actions ne crée pas encore de capacité réelle.
La solution la plus sûre consiste à établir une base séquentielle, puis à répartir les tests UI iOS de GitHub Actions avec Xcode Test Plans ou only-testing, avant de router chaque fragment vers un Mac distant indépendant. Avec un seul Runner libre, plusieurs jobs restent en file d’attente ; ils ne forment pas une exécution parallèle entre plusieurs nœuds.
Cet article s’adresse à trois profils :
- aux ingénieurs de test iOS qui veulent découper une suite XCTest sans perdre ses dépendances ;
- aux ingénieurs DevOps qui configurent une matrice, des étiquettes de Runner et la collecte des artefacts ;
- aux responsables techniques qui doivent décider s’il faut optimiser la suite, ajouter un Mac distant ou conserver une voie de validation séquentielle.
Première étape : mesurer la suite avant de la découper
La première erreur consiste à répartir une suite selon le nombre de fichiers. Deux fichiers peuvent contenir des tests très courts, tandis qu’un seul scénario de parcours utilisateur peut monopoliser le simulateur. Avant toute modification, exécutez donc la suite complète sur un même commit et consignez :
- l’ordre réel des tests ;
- la durée de chaque groupe ;
- le moment et le type de chaque échec ;
- le temps passé en attente d’un Runner ;
- le chemin du fichier
xcresult; - les captures d’écran, journaux et diagnostics disponibles.
Cette base sépare le temps de préparation du projet du temps de test. Un build long ne prouve pas que les tests UI sont le goulot d’étranglement. De même, un job GitHub Actions en cours ne signifie pas forcément qu’un test s’exécute : le job peut attendre un Runner, installer des dépendances ou préparer un simulateur.
La documentation officielle de GitHub Actions et de son déroulement distingue le workflow, les jobs et les runners. Utilisez cette distinction dans votre relevé :
- Concurrence des jobs : plusieurs jobs peuvent être créés par la matrice.
- Concurrence des nœuds : plusieurs Mac peuvent exécuter ces jobs en même temps.
- Parallélisme interne de Xcode : un même Mac peut lancer plusieurs opérations de test, selon le projet, le simulateur et les ressources disponibles.
- Parallélisme des processus Swift Testing : il s’agit d’un comportement distinct, qui ne transforme pas automatiquement une machine en plusieurs Runners.
Ces niveaux ne sont pas interchangeables. Augmenter la taille d’une matrice sans augmenter le nombre de Runners disponibles augmente seulement la file d’attente.
Point de contrôle : si le temps d’attente du Runner domine votre mesure, ne modifiez pas encore les tests. Si les tests échouent avant même l’exécution des assertions, examinez d’abord l’environnement du simulateur, les certificats, les données de test et les permissions du nœud.
Deuxième étape : choisir des frontières de test reproductibles
Comment regrouper les tests avec Xcode Test Plans ?
Utilisez Xcode Test Plans, les cibles de test, les suites ou only-testing pour définir des groupes compréhensibles. Chaque groupe doit avoir une responsabilité claire : authentification, parcours d’achat, recherche, synchronisation, lecture audio ou export vidéo, par exemple.
Le but n’est pas de fabriquer des fragments de taille identique. Le but est de créer des unités qui peuvent :
- démarrer dans un état connu ;
- recevoir des données d’entrée documentées ;
- s’exécuter sans attendre le résultat d’un autre fragment ;
- être relancées seules avec une commande identifiable ;
- produire un résultat exploitable par un développeur.
Les tests qui partagent un compte, une session persistante, une base de données mutable, un environnement de notification ou une séquence métier doivent rester groupés jusqu’à ce que cette dépendance soit supprimée. Répartir artificiellement un test de connexion dans un fragment et le test suivant dans un autre crée une dépendance cachée. Le résultat sera fragile, même si chaque job est techniquement « vert ».
Apple décrit l’organisation des tests et l’usage des sélections de tests dans sa documentation sur l’organisation des tests pour améliorer le retour d’information. Servez-vous de cette documentation pour vérifier la structure retenue, puis confirmez les paramètres disponibles sur votre nœud avec xcodebuild -help.
Pour chaque fragment, rédigez une fiche courte :
- identifiant stable, par exemple
ui-authouui-media; - Test Plan, Scheme et sélection utilisée ;
- destination du simulateur ;
- données ou comptes nécessaires ;
- propriétaire du fragment ;
- commande de reproduction locale ou distante ;
- condition qui justifie son maintien ou sa fusion.
Comment éviter les tests oubliés ou exécutés deux fois ?
Après le premier découpage, lancez encore la suite complète. Comparez la liste des tests attendus avec la liste réellement exécutée dans chaque fragment. Contrôlez également les doublons causés par une sélection trop large au niveau du Scheme et une sélection plus précise au niveau de only-testing.
Un fragment acceptable n’est donc pas seulement « rapide ». Il est traçable. Vous devez pouvoir répondre à trois questions : quel test appartient à ce groupe, quelles données lui sont nécessaires et comment le relancer sans exécuter toute la suite.
Troisième étape : créer la matrice GitHub Actions sans confondre jobs et capacité
Comment faire fonctionner plusieurs suites iOS en parallèle ?
La matrice GitHub Actions doit transformer chaque identifiant de fragment en job distinct. Le fichier de workflow peut utiliser des valeurs génériques telles que :
ui-auth;ui-catalogue;ui-media;ui-checkout.
Les noms de dépôt, de Runner, de Scheme, de Test Plan, de périphérique et de chemin doivent rester des paramètres de votre environnement. Évitez de copier un exemple avec des noms réels dans votre pipeline : cela rend souvent la migration plus difficile et masque les prérequis.
La syntaxe officielle des workflows GitHub Actions couvre la stratégie de matrice et le contrôle de concurrence. Vérifiez notamment les différences entre :
- le nombre de combinaisons produites par
matrix; - la limite configurée par
max-parallel; - les groupes
concurrencyqui peuvent annuler ou bloquer des exécutions ; - le nombre de Runners réellement disponibles ;
- les étiquettes compatibles avec chaque Runner.
Le routage vers un Mac distant doit correspondre à l’environnement réel. Une étiquette peut représenter une famille de version Xcode, une architecture Apple Silicon, une destination de simulateur ou un groupe réservé à l’équipe mobile. La documentation GitHub sur l’utilisation des étiquettes de Runners auto-hébergés rappelle que le job doit trouver un Runner correspondant à toutes les étiquettes demandées.
Si aucun nœud ne correspond, le job attend. Si un seul nœud correspond, les fragments sont sérialisés sur ce nœud. La matrice existe bien, mais elle ne fournit pas le gain attendu.
Faut-il reconstruire l’application dans chaque fragment ?
Deux modèles sont possibles.
Le modèle le plus simple reconstruit l’application dans chaque job. Il est plus facile à comprendre et limite les dépendances entre jobs, mais il répète la préparation et peut rendre les résultats moins lisibles lorsque le build est la partie dominante.
L’autre modèle utilise build-for-testing, conserve les produits nécessaires, puis les distribue aux jobs de test. Il réduit la répétition lorsque le produit de build est fiable, mais ajoute la gestion des artefacts, des chemins, de la compatibilité du nœud et de la rétention. Le choix ne doit pas être fondé sur une promesse de vitesse abstraite : comparez la complexité de récupération et la facilité de reproduction d’un échec.
Pour une première mise en service, gardez la voie la plus facile à diagnostiquer. Vous pourrez ensuite séparer la construction et l’exécution lorsque la base séquentielle aura prouvé que le produit distribué reste identique.
Quatrième étape : isoler les simulateurs avant d’augmenter la concurrence
Une exécution distribuée ne doit pas partager implicitement ses fichiers temporaires. Donnez à chaque fragment un espace séparé pour :
- la destination du simulateur ;
DerivedData;- le fichier
xcresult; - les journaux de console ;
- les captures d’écran ;
- les ports utilisés par les services de test ;
- les comptes et données locales ;
- les fichiers de configuration générés.
Deux jobs sur le même Mac peuvent se perturber sans afficher immédiatement une erreur explicite. Un simulateur peut conserver une session, un chemin peut être écrasé, ou un test peut lire une base préparée par un autre fragment. Ces échecs donnent parfois l’impression que XCTest est instable, alors que le problème vient de l’isolation.
Apple documente le lancement des tests et l’interprétation des résultats dans sa référence sur l’exécution des tests et les résultats. Utilisez les informations disponibles dans le résultat plutôt que le seul code de sortie du job.
Commencez par un seul fragment sur un seul Mac distant. Ajoutez ensuite un second fragment. Ne changez pas en même temps la destination, la version du projet, le mode de build et le nombre de jobs. Si vous modifiez plusieurs variables, vous ne saurez pas si l’échec vient d’une ressource saturée, d’une donnée partagée ou d’un test non déterministe.
Sur un même Mac, le parallélisme interne de Xcode possède ses propres limites. La mémoire, le stockage, les processus du simulateur et les services auxiliaires peuvent devenir le facteur dominant. Plusieurs Mac distants offrent une isolation plus claire, mais exigent une capacité de Runner réellement disponible pour chaque job.
Expérience de terrain à appliquer : lorsque deux fragments échouent simultanément, comparez leurs chemins de résultats, leurs destinations et leurs comptes de test avant de conclure à un défaut du code. Un conflit de fichier peut produire deux échecs qui semblent indépendants.
Cinquième étape : agréger les résultats sans masquer la cause
Chaque job de la matrice doit téléverser ses propres artefacts, même en cas d’échec. Conservez au minimum :
- le fichier
xcresult; - le journal de commande ;
- les captures d’écran et vidéos disponibles ;
- l’identifiant du fragment ;
- la destination utilisée ;
- la révision du code ;
- le statut de préparation du Runner.
Le job de synthèse doit d’abord vérifier que tous les fragments attendus ont produit un résultat. Un fragment manquant n’est pas un test réussi. Il peut signaler une panne du Runner, une annulation de workflow, un problème de permission ou un échec avant le lancement de xcodebuild.
Séparez ensuite trois catégories :
- échec d’infrastructure : Runner indisponible, simulateur impossible à démarrer, espace disque insuffisant ou artefact introuvable ;
- échec d’assertion : le test a réellement exécuté son scénario et une vérification a échoué ;
- échec intermittent : le même commit produit des résultats différents sans changement déclaré d’environnement.
La documentation Apple sur l’interprétation des résultats de test est la référence à consulter pour comprendre les informations contenues dans les résultats. Pour la couverture, ne faites pas la somme mécanique de plusieurs pourcentages. Vérifiez d’abord si les rapports couvrent des fichiers et des lignes distincts, puis utilisez l’outil officiel adapté à votre format de résultat.
Un nouvel essai peut aider à identifier une instabilité. Il ne doit pas remplacer le premier échec dans le rapport final. Conservez les deux statuts et placez les tests suspects dans une liste séparée, avec un responsable et une action corrective. Sinon, la matrice donnera une impression de fiabilité supérieure à celle réellement observée.
Quelle stratégie choisir après le premier essai ?
Utilisez ces conditions de décision après plusieurs exécutions représentatives de vos Pull Requests :
- Si les fragments sont indépendants, que plusieurs Runners correspondants sont libres et que la file d’attente diminue, conservez la matrice distribuée et augmentez progressivement sa couverture.
- Si les jobs sont créés mais attendent un Runner, réduisez temporairement la matrice ou ajoutez une capacité Mac seulement après avoir confirmé que l’attente est bien le goulot d’étranglement.
- Si les échecs apparaissent uniquement en concurrence, revenez à une exécution isolée, puis examinez simulateurs, comptes, ports, données et chemins de résultats.
- Si un fragment domine durablement le temps total, découpez sa responsabilité selon les dépendances fonctionnelles, et non selon le nombre de fichiers.
- Si les tests ne sont pas reproductibles en mode séquentiel, ne passez pas au multi-nœud : vous ne pourrez pas attribuer correctement les échecs.
- Si les tests critiques concernent une publication, gardez une voie séquentielle ou un contrôle double jusqu’à ce que les résultats distribués soient comparables.
- Si un seul Mac Runner reste disponible, utilisez la matrice pour organiser les sélections et les artefacts, mais ne la présentez pas comme une capacité de test inter-nœuds.
Cette dernière règle répond directement au cas fréquent d’une équipe qui possède un seul Mac loué. Une matrice à plusieurs jobs peut améliorer la lisibilité du pipeline, mais elle ne supprime pas la file d’attente physique.
Comparaison avant la mise en production
| Approche | Quand la retenir | Avantage principal | Risque à contrôler |
|---|---|---|---|
| Suite séquentielle sur un Mac distant | Base instable ou dépendances nombreuses | Diagnostic simple et ordre maîtrisé | Retour plus lent |
| Parallélisme interne sur un seul Mac | Tests isolés et ressources suffisantes | Mise en place limitée | Conflits de simulateur et de stockage |
| Matrice sur plusieurs Mac distants | Fragments indépendants et Runners disponibles | Exécution distribuée et meilleure isolation | File d’attente si la capacité réelle est insuffisante |
| Double voie séquentielle et distribuée | Tests de publication ou migration progressive | Comparaison et retour arrière | Coût d’entretien de deux chemins |
Si vous cherchez un nœud accessible pour valider votre environnement, vous pouvez examiner les solutions de Mac distant proposées par KVMNODE. Pour un besoin ciblé autour d’un Mac mini, la page Mac mini pour les charges de commande et de test permet de comparer le point de départ matériel avec votre besoin de Runner. La décision doit toutefois venir de vos mesures : file d’attente, stabilité, artefacts et possibilité de reproduire un échec.
Dans la pratique, une infrastructure Linux ou Windows peut piloter GitHub Actions, mais elle ne remplace pas le nœud macOS requis pour Xcode, XCTest et les simulateurs iOS. Une machine virtuelle ou un poste partagé ajoute souvent des difficultés d’accès au simulateur, de permissions, de persistance et de diagnostic. L’achat d’un Mac peut être préférable pour une charge permanente et prévisible, mais il immobilise le matériel et vous laisse gérer sa maintenance, sa disponibilité et son accès réseau.
Pour une expérimentation courte, un pipeline de validation ou une montée en charge progressive, la location d’un Mac distant KVMNODE est généralement plus souple : vous pouvez commencer par mesurer une suite sur un nœud, ajouter un second nœud seulement si la file d’attente le justifie, et conserver vos contrôles d’accès sans acheter immédiatement plusieurs machines. Elle n’est pas le meilleur choix si vous avez besoin d’un accès physique continu, d’une charge lourde stable à long terme ou d’un matériel entièrement administré sur site.
Commencez par une base séquentielle, passez à deux fragments indépendants, puis observez séparément les jobs, les Runners, les simulateurs et les résultats. C’est cette progression qui permet de savoir si vous avez besoin de mieux découper vos tests ou de plusieurs Mac distants, plutôt que de confondre une grande matrice avec une capacité réellement disponible.