Les charges utiles de diagnostic MetricKit n’arrivent pas au rythme des commits. Une simple modification apportée aujourd’hui au parseur peut entraîner la perte silencieuse de champs, sans que le problème soit détecté avant la réception d’un rapport de plantage ou de blocage plusieurs jours plus tard. Une approche plus fiable consiste à anonymiser, normaliser et conserver les charges utiles déjà collectées sous forme d’échantillons fixes, puis à rejouer le même jeu d’entrées à chaque commit sur un Mac dans le cloud. On teste ainsi la chaîne de traitement des diagnostics elle-même, sans attendre le prochain incident aléatoire.
Définir d’abord le périmètre des tests
Les échantillons fixes conviennent à quatre niveaux de vérification : la lecture du JSON brut, la correspondance entre les champs système et le modèle interne, la suppression des données sensibles et le repli en cas d’entrée anormale. Ils ne permettent pas de prouver que le système produira effectivement une charge utile et ne remplacent pas la validation des callbacks sur l’appareil.
Il est recommandé de séparer le code de réception du traitement métier. La couche de réception se contente d’obtenir jsonRepresentation(), de l’écrire dans un répertoire protégé et de planifier son envoi. La couche d’analyse reçoit des Data et produit des structures internes indépendantes des types MetricKit. Les tests unitaires n’appellent que cette seconde couche, ce qui évite d’avoir à fabriquer de faux objets système dans la cible de test.
Un échantillon ne sert pas simplement à reproduire une analyse réussie. Il fige le contrat d’entrée afin que chaque modification du parseur permette de déterminer quels champs ont changé et quelles informations ont été écartées.
Créer des échantillons normalisés pouvant être versionnés
Une charge utile brute peut contenir l’identifiant du bundle, des informations sur l’appareil, des horodatages, des symboles de pile d’appels et des chemins locaux. Ne la versionnez jamais telle quelle. Conservez d’abord l’original dans un environnement restreint, puis générez une copie normalisée pouvant rejoindre le dépôt : remplacez les dates par des valeurs fixes, les identifiants par des valeurs de test et les chemins par $APP ou $HOME. Les adresses de pile doivent conserver leur format, mais pas leur valeur réelle.
Vous pouvez ajouter un champ schema au format interne, sans modifier la version MetricKit d’origine. Organisez les répertoires par type de diagnostic :
Tests/Fixtures/MetricKit/
├── crash/basic.json
├── crash/missing-stack.json
├── hang/main-thread.json
├── disk-write/threshold.json
└── malformed/truncated.json
Chaque échantillon ne doit représenter qu’une seule condition. Si un même fichier contient à la fois un plantage, un blocage et une anomalie d’écriture sur disque, il sera difficile d’identifier la cause d’un échec. Le nom de l’échantillon doit décrire l’entrée, et non le résultat attendu. Placez les valeurs attendues dans le code de test afin qu’une modification opportuniste des assertions soit plus facile à repérer lors de la revue.
Appliquer un contrôle structurel avant les tests
Un contrôle peu coûteux avec jq, exécuté avant la compilation des tests, permet de bloquer rapidement les fichiers JSON invalides, les versions manquantes et les chemins non anonymisés. Le tableau diagnostics ci-dessous correspond au format normalisé propre à l’équipe ; il ne suppose pas que les charges utiles brutes du système possèdent la même structure.
set -euo pipefail
root="Tests/Fixtures/MetricKit"
find "$root" -name '*.json' -print0 |
while IFS= read -r -d '' file; do
jq -e '
type == "object" and
.schema == 1 and
(.diagnostics | type == "array") and
all(.diagnostics[];
(.kind | type == "string") and
(.timestamp | type == "string") and
(.stackID | type == "string")
)
' "$file" >/dev/null
if grep -E '/Users/|/private/var/|[A-F0-9]{16,}' "$file"; then
echo "fixture contains unnormalized data: $file" >&2
exit 1
fi
done
Ce contrôle structurel ne doit pas imposer la présence de tous les champs système, faute de quoi chaque nouveau champ facultatif provoquerait des échecs sans intérêt. Vérifiez uniquement les clés dont dépend réellement le traitement interne et configurez le décodeur pour qu’il ignore les champs inconnus.
Couvrir le contrat du parseur avec des échantillons positifs et négatifs
Préparez au minimum un cas d’entrée valide et trois cas d’échec. L’objectif n’est pas seulement de vérifier qu’aucune exception n’est levée, mais de s’assurer que la sortie reste exploitable pour l’agrégation, les alertes et l’analyse des incidents.
| Échantillon | Comportement attendu | Comportement à éviter |
|---|---|---|
| Plantage complet | Produire le type, l’horodatage et l’identifiant de pile | Conserver le chemin local d’origine |
| Tableau de diagnostics vide | Renvoyer un résultat vide | Le traiter comme un échec de décodage |
| Pile d’appels absente | Marquer le diagnostic comme incomplet | Présenter une fausse pile vide comme une donnée normale |
| JSON tronqué | Renvoyer une erreur pouvant être classée | Arrêter directement le processus |
| Type inconnu | Enregistrer une valeur d’énumération inconnue | Écarter tout le lot de charges utiles |
Vérifier le modèle interne plutôt que le JSON complet
Les snapshots portant sur l’intégralité du JSON sont facilement perturbés par l’ordre des champs et les métadonnées sans importance. Vérifiez en priorité le nombre de diagnostics, leur type, leurs identifiants stables et le résultat de l’anonymisation. N’ajoutez un snapshot du JSON formaté que si la sortie normalisée doit être échangée entre plusieurs systèmes. Les erreurs doivent également prendre la forme d’énumérations comparables, telles que invalidJSON, missingRequiredField ou unsupportedDiagnostic, plutôt que de simples messages en langage naturel susceptibles de changer.
Intégrer les tests à la CI sur Mac dans le cloud
Sur les Mac cloud de VMRunner, exécutez le contrôle des fixtures avant les tests unitaires et assurez-vous que les tâches non interactives utilisent un répertoire de travail fixe. L’ordre d’exécution peut être le suivant : récupérer le code, lancer le contrôle structurel, exécuter les tests unitaires du parseur, générer les résultats de test, puis vérifier qu’aucune modification non validée des échantillons n’est apparue dans l’espace de travail.
Toute modification d’un échantillon doit faire l’objet d’une revue distincte. Lorsqu’un nouveau champ système apparaît, commencez par déterminer si le parseur doit l’exploiter. Si oui, mettez à niveau le schema interne et ajoutez les tests de migration dans le même commit. Sinon, conservez un décodage permissif. N’utilisez pas de script pour écraser automatiquement les fichiers de référence dans la CI : une véritable perte de champs risquerait alors d’être « approuvée » par de nouveaux résultats erronés.
Liste de contrôle avant fusion
- Les charges utiles brutes ont été anonymisées en dehors du dépôt.
- Chaque échantillon ne couvre qu’une seule condition de diagnostic.
- Les cas normal, vide, avec champ manquant, tronqué et de type inconnu sont tous testés.
- Les champs facultatifs inconnus ne provoquent pas l’échec de tout le lot.
- Les chemins locaux, les identifiants longs et le contenu utilisateur ne peuvent pas franchir le contrôle.
- Les échecs d’analyse renvoient des catégories d’erreur stables.
- Les modifications des fixtures et du parseur sont validées lors de la même revue.
Une fois ces contraintes en place, le traitement des diagnostics MetricKit ne consiste plus à « essayer après réception des données » : il devient un test d’ingénierie ordinaire et reproductible. Les charges utiles système doivent toujours être validées sur appareil, mais l’analyse, l’anonymisation et la compatibilité ne dépendent plus de l’arrivée aléatoire de nouveaux rapports.
Questions fréquentes
Les fixtures MetricKit remplacent-elles les tests sur appareil ?
Non. Elles valident le décodeur, l’anonymisation et les transformations, mais la livraison effective des charges par le système doit encore être contrôlée sur des appareils et par l’observation en production.
Faut-il conserver directement le JSON MetricKit brut dans Git ?
Non par défaut. Conservez les données brutes anonymisées dans un espace restreint et ne versionnez que des fixtures normalisées sans identifiants, chemins locaux ni contenu utilisateur.
Un nouveau champ inconnu doit-il faire échouer la CI ?
En général non. Le décodeur doit tolérer les champs optionnels inconnus, mais échouer si un champ indispensable au contrat interne, comme le type ou l’identifiant de pile, disparaît.
Lancez votre prochaine compilation sur un Mac dédié dans le cloud
Choisissez le modèle, le nœud et la période de facturation. La configuration et le montant en dollars sont détaillés avant la commande ; la disponibilité affichée dans la console fait foi.