Lorsqu’un même commit exécute tous les tests sur la machine du développeur, mais seulement une partie d’entre eux sur un Mac distant, la cause la plus fréquente n’est pas le code de test. C’est souvent le fichier .xctestplan qui a discrètement dérivé : quelqu’un a temporairement ignoré des tests dans Xcode, désactivé des diagnostics ou enregistré dans le plan des variables d’environnement propres à sa machine. La solution consiste à traiter XCTestPlan comme une entrée de build, et non comme un réglage annexe de l’interface graphique.
Commencer par délimiter le contrat de test
Un point d’entrée de test reproductible comprend au minimum un Scheme partagé, un XCTestPlan, une destination d’exécution et des arguments de ligne de commande. Vérifiez d’abord que le Scheme et le plan sont bien détectés en ligne de commande :
WORKSPACE="${WORKSPACE:-App.xcworkspace}"
SCHEME="${SCHEME:-App}"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-showTestPlans
Si le plan attendu n’apparaît pas dans la sortie, vérifiez d’abord que le Scheme est défini comme Shared, que le fichier de plan est ajouté au dépôt et que l’action Test du Scheme y fait référence. Ne compensez pas temporairement l’absence du plan avec un argument de CI : le point d’entrée local et le pipeline continueraient alors à diverger.
Les éléments suivants doivent être explicitement examinés dans le plan :
| Élément | Fait à vérifier | Dérive fréquente |
|---|---|---|
| Test Targets | Quels tests unitaires et tests d’interface sont exécutés | Nouvelle cible non ajoutée |
| Selected Tests | Seuls les tests spécifiés sont-ils exécutés ? | Sélection de débogage intégrée au commit |
| Skipped Tests | Chaque exclusion a-t-elle un responsable et une échéance ? | Test défaillant masqué définitivement |
| Configurations | Langue, région et arguments de lancement | Configuration locale remplaçant la configuration par défaut |
| Diagnostics | Stratégie de diagnostic des plantages, des threads et des performances | Désactivation accidentelle pour accélérer l’exécution |
| Parallelization | Quelles cibles peuvent être exécutées en parallèle ? | Interférences entre tests partageant un même état |
Ignorer un test ne constitue pas une correction. Tout nouveau skipped test doit être soumis à une revue comme une modification de code, avec les conditions de sa réactivation.
Générer un diff normalisé et lisible
XCTestPlan est un fichier structuré. L’examen direct du texte brut est facilement perturbé par l’ordre des champs et les identifiants générés automatiquement. Vous pouvez conserver dans le dépôt une référence normalisée qui supprime uniquement les identifiants de configuration sans incidence sur la sémantique d’exécution :
PLAN="App.xctestplan"
CURRENT=".ci/xctestplan.current.json"
BASELINE=".ci/xctestplan.baseline.json"
mkdir -p .ci
plutil -convert json -o - "$PLAN" |
jq -S 'del(.configurations[]?.id)' > "$CURRENT"
diff -u "$BASELINE" "$CURRENT"
Lors de la première intégration, examinez manuellement $CURRENT, puis copiez-le comme référence et validez-le dans le dépôt. Par la suite, le pipeline génère uniquement le fichier courant et exécute diff. Les cibles, les exclusions, les variables d’environnement, les arguments et les options de diagnostic doivent tous être conservés. Ne supprimez pas d’autres champs fonctionnels dans le seul but d’obtenir un « diff propre ».
Le script de normalisation fait lui aussi partie de l’infrastructure de test. Ses modifications et celles du plan doivent apparaître dans la même demande de fusion. Sinon, un simple assouplissement des règles de filtrage risque de rendre invisibles toutes les dérives ultérieures.
Bloquer les valeurs sensibles et les dépendances à l’hôte
Le fichier de plan convient au stockage des noms de variables, mais pas à celui des jetons, mots de passe, contenus de clés privées ou répertoires de développeurs. Commencez par extraire récursivement les variables d’environnement activées :
plutil -convert json -o - App.xctestplan |
jq -r '
.. |
objects |
.environmentVariableEntries? // empty |
.[]? |
select(.enabled == true) |
[.key, .value] |
@tsv
'
Lors de l’examen de la sortie, bloquez en priorité trois catégories de contenu : les longues chaînes ressemblant à des clés, les chemins absolus de la forme /Users/某人/ et les chemins d’outils disponibles uniquement dans un Shell interactif. Les véritables valeurs sensibles doivent être injectées à l’exécution par l’environnement contrôlé de la CI. Le code de test doit uniquement lire les noms de variables et produire une erreur explicite lorsqu’une valeur manque.
Éviter une priorité de surcharge incontrôlée
XCTestPlan, le Scheme, les arguments de xcodebuild et le code de test peuvent tous définir des arguments de lancement. Il est recommandé d’imposer un ordre de priorité unique : le plan conserve les valeurs par défaut stables, la CI injecte uniquement les valeurs sensibles et l’identifiant de l’exécution en cours, et le code de test ne modifie pas la configuration au niveau du processus. Si le pipeline utilise -only-testing ou -skip-testing, ces arguments doivent figurer dans un script soumis à revue et non être dissimulés dans un champ de saisie temporaire du panneau de tâches.
Exécuter sur une destination fixe et conserver les preuves
Utilisez d’abord xcrun simctl list devices available pour sélectionner un appareil installé sur le nœud actuel, puis transmettez son UDID comme entrée du pipeline. Fixer l’appareil et le runtime facilite davantage l’explication des écarts qu’une désignation vague telle que « système le plus récent ».
DEVICE_UDID="${DEVICE_UDID:?Set DEVICE_UDID from simctl}"
RESULT_PATH="${RESULT_PATH:-artifacts/CI.xcresult}"
rm -rf "$RESULT_PATH"
mkdir -p "$(dirname "$RESULT_PATH")"
xcodebuild test \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-testPlan CI \
-destination "platform=iOS Simulator,id=$DEVICE_UDID" \
-resultBundlePath "$RESULT_PATH"
Que l’exécution réussisse ou échoue, conservez le fichier .xcresult, la commande complète, l’identifiant du commit, la version de Xcode et l’UDID de l’appareil sélectionné. Ne gardez pas uniquement les dernières dizaines de lignes du journal : l’absence d’exécution d’un test, le plantage d’un processus et l’échec d’une assertion nécessitent des preuves différentes. Le bundle de résultats préserve la hiérarchie des tests, les pièces jointes et les informations de diagnostic.
Commencez avec des paramètres de parallélisation prudents. Les cibles de test qui dépendent de la même base de données, d’un port fixe ou de fichiers partagés ne doivent pas être immédiatement exécutées en parallèle. Isolez d’abord complètement les états, puis autorisez progressivement la parallélisation, au lieu de masquer les conditions de concurrence avec des relances.
Transformer l’audit en garde de fusion
La garde finale doit suivre un ordre fixe : vérifier que le Scheme détecte le plan, générer le fichier normalisé, le comparer à la référence, rechercher les valeurs sensibles et les chemins absolus, contrôler la liste des exclusions, puis seulement exécuter les tests. Les erreurs de configuration sont ainsi détectées avant le démarrage du simulateur, ce qui réduit le temps d’attente et de diagnostic.
Avant de valider un commit, utilisez la liste suivante :
- Le Scheme est partagé et le fichier de plan est sous contrôle de version.
- Les nouvelles cibles de test ont été ajoutées au plan approprié.
- Tous les skipped tests ont un motif, un responsable et des conditions de réactivation.
- Le plan ne contient aucune valeur sensible réelle ni aucun répertoire personnel.
- La CI ne contient aucun argument caché modifiant le périmètre des tests.
- L’UDID de l’appareil, la version de Xcode et le bundle de résultats sont enregistrés.
- Les règles de normalisation ne suppriment aucun champ ayant une incidence sur la sémantique d’exécution.
- Le fichier
.xcresultest archivé même en cas d’échec.
Lorsque chaque modification de XCTestPlan devient compréhensible lors de la revue de code, le scénario « tout passe en local, mais certains tests manquent sur le Mac distant » cesse d’être un mystère intermittent. Il devient un écart de configuration qui peut être bloqué avant l’exécution.
Questions fréquentes
Pourquoi un schéma Xcode partagé ne suffit-il pas ?
Le schéma définit l’entrée, mais XCTestPlan peut encore modifier les cibles, configurations, arguments, variables, langues et tests exclus. La CI doit verrouiller ces trois niveaux.
Peut-on enregistrer un jeton d’accès dans XCTestPlan ?
Non. Le plan ne doit contenir que le nom de la variable ou une valeur sans risque. Le secret est injecté à l’exécution par l’environnement CI et ne doit apparaître dans aucun journal.
La normalisation peut-elle masquer une modification importante ?
Oui si le filtre est trop large. Supprimez uniquement les identifiants générés sans effet sémantique et conservez les cibles, exclusions, options, diagnostics et noms de configuration.
Configurez un Mac cloud fixe pour vos pipelines de développement
MangoVM propose des nœuds physiques Apple Silicon en deux versions : M4 et M4 Pro. Choisissez la durée de location, la région et les options de stockage pour vérifier le récapitulatif complet de votre commande.