Guide d’ingénierie

Créer une matrice de tests simulateur iOS sur un Mac cloud

Créer une matrice de tests simulateur iOS sur un Mac cloud

Une même suite de tests d’interface peut réussir en local, puis expirer de façon aléatoire sur un nœud distant. Le problème vient souvent moins du code métier que d’un état résiduel du simulateur, de limites de lots qui changent entre les exécutions ou d’un niveau de parallélisme excessif. Une matrice de tests fiable doit permettre de répondre à trois questions après chaque exécution : quel environnement d’exécution a été utilisé, quels tests ont été regroupés et où les données de diagnostic ont été enregistrées. Voici comment mettre en place un processus reproductible avec xcodebuild et simctl.

Définir d’abord les limites de la matrice

Ne commencez pas par parcourir toutes les combinaisons d’appareils et de versions du système. Sélectionnez d’abord un environnement d’exécution principal et un appareil représentatif afin d’établir une base stable, puis ajoutez progressivement les configurations nécessaires aux tests de compatibilité. Avant chaque exécution, consignez la version de Xcode, la version de macOS, l’identifiant de l’environnement d’exécution, le commit et la liste des tests. Le nom d’un appareil ne constitue pas une clé unique fiable, car plusieurs simulateurs peuvent porter le même nom. Enregistrez plutôt leur UDID.

Dimension Configuration de référence Condition d’extension
Environnement d’exécution Version principale prise en charge par le projet Couvrir la version minimale prise en charge avant une livraison
Type d’appareil Une taille d’écran courante Ajouter d’autres tailles lorsque la mise en page doit être adaptée
Lots de tests Groupes fixes par classe de tests Scinder davantage lorsqu’un lot devient trop long
Niveau de parallélisme Commencer avec deux lots Augmenter progressivement une fois l’utilisation des ressources stabilisée

L’objectif d’une matrice de tests n’est pas de multiplier les combinaisons, mais d’obtenir des résultats explicables pour un même commit soumis aux mêmes entrées.

Créer une base de simulateurs jetables

Commencez par interroger les environnements d’exécution et les types d’appareils réellement disponibles sur le nœud. N’inscrivez pas les numéros de version en dur dans le script.

xcrun simctl list runtimes
xcrun simctl list devicetypes
xcrun simctl list devices available

Sélectionnez les identifiants appropriés dans la sortie, puis créez un appareil distinct pour chaque lot. Le nom sert uniquement à faciliter la lecture ; toutes les commandes suivantes doivent utiliser l’UDID.

RUNTIME_ID="com.apple.CoreSimulator.SimRuntime.iOS-XX-X"
DEVICE_TYPE_ID="com.apple.CoreSimulator.SimDeviceType.iPhone-XX"

UDID=$(xcrun simctl create "ui-shard-01" "$DEVICE_TYPE_ID" "$RUNTIME_ID")
xcrun simctl boot "$UDID"
xcrun simctl bootstatus "$UDID" -b

Ne réutilisez pas les simulateurs employés quotidiennement par les développeurs pour le débogage. Les tests peuvent modifier les autorisations, la région, le clavier, les données de l’application et les tâches en arrière-plan. Des appareils jetables limitent ces changements d’état à une seule exécution. Si les tests dépendent d’autorisations liées à l’appareil photo, aux notifications ou à la localisation, configurez-les explicitement pendant la phase de préparation au lieu de supposer qu’une autorisation accordée précédemment existe encore.

Compiler une fois, puis répartir les tests selon une liste fixe

Chaque lot de la matrice ne doit pas relancer la compilation. Exécutez d’abord build-for-testing afin de produire un artefact de test commun, puis utilisez test-without-building pour chaque lot.

DERIVED_PATH="$PWD/.derived-test"

xcodebuild build-for-testing \
  -workspace App.xcworkspace \
  -scheme AppUITests \
  -destination "generic/platform=iOS Simulator" \
  -derivedDataPath "$DERIVED_PATH"

XCTESTRUN=$(find "$DERIVED_PATH" -name "*.xctestrun" -print -quit)

Les listes définissant les lots doivent être versionnées dans le dépôt. Cela évite que leurs limites changent à chaque exécution à cause d’un découpage improvisé par ordre alphabétique ou par durée. Par exemple, regroupez les tests de connexion et de déconnexion dans un lot, puis les tests hors ligne et de synchronisation dans un autre. Lors de l’exécution, désactivez le parallélisme interne de Xcode afin que la concurrence soit contrôlée uniquement par l’orchestrateur externe.

xcodebuild test-without-building \
  -xctestrun "$XCTESTRUN" \
  -destination "platform=iOS Simulator,id=$UDID" \
  -only-testing:"AppUITests/AuthenticationTests" \
  -parallel-testing-enabled NO \
  -resultBundlePath "$PWD/results/shard-01.xcresult"

Si les durées diffèrent fortement d’un lot à l’autre, ajustez les listes à partir de la durée médiane des dernières exécutions. Une fois la répartition modifiée, versionnez-la et consignez le changement. Ne laissez pas le script d’orchestration réorganiser automatiquement les lots avant chaque exécution, car il deviendrait impossible de comparer directement un échec avec l’exécution précédente.

Maîtriser le parallélisme et garantir le nettoyage

Ajouter des simulateurs en parallèle n’accélère pas nécessairement les tests. Chaque lot supplémentaire lance davantage de processus de démarrage, de services graphiques, de processus de test et d’instances de l’application. Commencez avec deux lots et surveillez la pression mémoire, la saturation du processeur, le temps de démarrage et la durée totale. Réduisez immédiatement le parallélisme en cas de swap, d’expiration de bootstatus ou de fortes variations dans la durée des tests.

Le script doit effectuer le nettoyage après une réussite, un échec ou une interruption. Enregistrez un gestionnaire de sortie pour les UDID créés, puis arrêtez et supprimez chaque appareil afin d’éviter qu’une exécution ultérieure ne réutilise un ancien simulateur.

cleanup() {
  xcrun simctl shutdown "$UDID" >/dev/null 2>&1 || true
  xcrun simctl delete "$UDID" >/dev/null 2>&1 || true
}

trap cleanup EXIT INT TERM

N’utilisez pas de commande de suppression globale pour effacer tous les simulateurs du nœud, car d’autres tâches peuvent s’y exécuter en parallèle. Ne traitez que les UDID créés et enregistrés pour l’exécution en cours. Si vous devez conserver l’état d’un échec, archivez d’abord les journaux et les paquets de résultats, puis supprimez le simulateur.

Distinguer les régressions réelles des échecs intermittents grâce aux résultats

Chaque lot doit disposer de son propre chemin .xcresult. Enregistrez également la sortie standard, l’heure de début, l’heure de fin, le code de sortie et l’UDID. La réutilisation d’un même chemin risque d’écraser les données du premier échec, souvent les plus précieuses. Après un échec, ne relancez qu’une seule fois le lot concerné au lieu de réexécuter immédiatement toute la matrice.

Un premier échec suivi d’une réussite indique généralement une condition d’attente instable, une animation, une dépendance réseau ou une course sur un état partagé. Deux échecs au même endroit signalent plus probablement une régression déterministe. Si les deux échecs surviennent à des étapes différentes, examinez en priorité les ressources du nœud et les états partagés entre les tests.

Avant la mise en production, vérifiez les points suivants dans cet ordre :

  1. Les environnements d’exécution et les types d’appareils proviennent bien des résultats interrogés sur le nœud actuel.
  2. Chaque lot utilise un UDID distinct et un répertoire de résultats séparé.
  3. Les artefacts ne sont compilés qu’une seule fois et les lots ne relancent pas la compilation.
  4. Les listes de tests sont versionnées et ne sont pas réorganisées aléatoirement avant l’exécution.
  5. Les résultats du premier échec ne sont pas écrasés par une nouvelle tentative.
  6. Après une interruption, seuls les simulateurs créés pour l’exécution en cours sont supprimés.

Questions fréquentes

Combien de simulateurs iOS faut-il exécuter en parallèle sur un Mac cloud ?

Commencez par deux lots, puis observez la pression mémoire, le processeur, le temps de démarrage et la variation de durée. Réduisez la concurrence dès que la mémoire permute ou que les démarrages expirent.

Pourquoi attribuer un simulateur distinct à chaque lot de tests ?

Cela isole les données de l’application, les autorisations et les processus en arrière-plan. Chaque environnement peut ensuite être supprimé sans affecter les autres lots.

Faut-il relancer toute la suite après un échec ?

Non. Conservez le premier fichier xcresult et ne relancez qu’une fois le lot en échec. S’il réussit, classez le test comme potentiellement instable et comparez les deux résultats.

ArmMacs Cloud Mac

Utilisez un nœud physique dédié selon la durée de votre projet

Choisissez la puce, la mémoire, le stockage, la durée de location et un nœud disponible. La livraison démarre lorsque le stock est disponible.

Choisir un modèle et commander