Pratiques d’ingénierie

Tester la chronologie WidgetKit et les données partagées sur Mac cloud

Tester la chronologie WidgetKit et les données partagées sur Mac cloud

Après avoir enregistré le nombre de tâches à faire dans l’application principale, l’équipe constate que le widget du simulateur distant affiche toujours l’ancienne valeur. Multiplier les appels à l’API de rafraîchissement ne fait généralement qu’ajouter du bruit au diagnostic : l’écriture des données partagées, leur lecture par l’extension, la génération de la chronologie et la décision du système de l’afficher sont quatre étapes distinctes. Prenons un widget WidgetKit qui affiche le nombre de tâches à faire et définissons un critère de vérification pour chacune.

Délimiter le transfert des données

L’application principale et l’extension du widget s’exécutent dans des processus distincts. Un simple UserDefaults.standard ne devient pas automatiquement une source de données commune. Activez d’abord le même App Group pour les deux targets du projet et placez son identifiant dans une configuration accessible aux deux. N’intégrez pas en dur un identifiant de test à la configuration de publication.

Une fois les données métier écrites, l’application principale enregistre le nombre et l’heure de mise à jour dans UserDefaults(suiteName:). L’extension les lit avec le même nom de suite. La fonction d’écriture doit signaler une réussite ou une erreur, et non conserver silencieusement l’ancienne valeur ; si la lecture échoue, l’extension doit afficher un état par défaut explicite. Si les données comportent plus d’un entier, encodez-les dans un enregistrement versionné pour éviter, par exemple, d’associer un nombre actualisé à un ancien titre.

Une demande de rafraîchissement indique seulement au système que « les données ont peut-être changé ». Elle ne transfère pas les données de l’application principale vers l’extension et ne garantit pas que le widget sera redessiné immédiatement.

Après l’écriture, relisez une fois les données dans l’application principale, puis consignez à la lecture côté extension le numéro de version, sans données sensibles. Si les versions diffèrent, le problème se situe au transfert des données. Si elles sont identiques mais que l’écran ne correspond pas, examinez alors la chronologie et l’affichage. N’inscrivez ni le contenu des tâches, ni les informations de compte, ni l’enregistrement partagé complet dans les journaux de diagnostic.

Faire du calcul de la chronologie une fonction pure

Le contenu des entrées de chronologie ne doit pas dépendre de l’heure réelle à laquelle le test s’exécute. Le fournisseur se limite à lire les données et à appeler une fonction de calcul qui reçoit l’heure courante et l’instantané déjà lu. Le code suivant illustre cette séparation minimale ; les types peuvent être placés dans un fichier source commun à l’application principale et à l’extension.

import Foundation

struct CountEntry: Equatable {
    let date: Date
    let count: Int
    let revision: Int
}

func makeEntries(
    now: Date,
    count: Int,
    revision: Int
) -> [CountEntry] {
    [
        CountEntry(date: now, count: count, revision: revision),
        CountEntry(
            date: now.addingTimeInterval(15 * 60),
            count: count,
            revision: revision
        )
    ]
}

La deuxième entrée contient toujours le même instantané : quinze minutes écoulées ne permettent pas de déduire que le nombre de tâches aura changé. Une fois les entrées obtenues, le fournisseur peut définir une politique demandant une nouvelle chronologie après la dernière entrée. Cette politique indique le moment souhaité pour une vérification, pas une promesse de mise à jour de l’interface à la seconde près. Lors de la réception de nouvelles données métier, demandez à nouveau le chargement du type de widget concerné. Si le produit exige un compte à rebours précis, réexaminez ce besoin plutôt que de simuler un processus en temps réel avec des entrées de chronologie rapprochées.

Fixer les entrées et vérifier les résultats maîtrisables

Dans un test unitaire, fournissez une Date, un nombre et un numéro de révision fixes. Vérifiez que la fonction produit exactement deux entrées, que leurs dates sont strictement croissantes et qu’elles portent le même nombre et la même révision. Testez aussi le cas où le nombre vaut zéro : zéro doit être affiché comme une vraie valeur, sans être masqué par l’état par défaut réservé aux échecs de lecture. Ces tests ne lancent pas le simulateur et n’attendent pas la planification du système ; en cas d’échec, ils désignent directement la logique de calcul.

Vérifier le transfert réel dans un simulateur cloud

Commencez par utiliser xcrun simctl list devices available pour trouver un simulateur iOS disponible et son identifiant d’appareil. Fixez cet identifiant pour la compilation et les tests afin qu’un changement de machine d’exécution ne fasse pas correspondre le nom de l’appareil à un autre environnement d’exécution :

xcodebuild test \
  -project Widgets.xcodeproj \
  -scheme Widgets \
  -destination "platform=iOS Simulator,id=$SIM_ID" \
  -resultBundlePath "$PWD/widget-tests.xcresult"

Le nom du projet et le scheme sont des exemples : remplacez-les avant l’exécution par ceux qui existent dans le dépôt. La configuration de la tâche doit fournir explicitement SIM_ID. Utilisez un nouveau répertoire de résultats à chaque exécution pour éviter qu’un fichier .xcresult existant ne fasse échouer la commande prématurément. Une fois les tests unitaires de chronologie réussis, installez une version contenant l’application principale et l’extension, puis ajoutez le widget dans le simulateur. Vérifiez d’abord l’état par défaut ; écrivez ensuite une nouvelle révision depuis l’application principale et demandez un rafraîchissement ; comparez enfin la lecture consignée par l’extension avec l’affichage.

Pour valider l’interface, vérifiez uniquement le contenu final et le numéro de révision. N’imposez pas comme assertion stricte leur apparition « dans les quelques secondes suivant la demande ». Le système peut modifier le moment du rafraîchissement. Si un test doit vérifier précisément un affichage immédiat, testez une vue d’aperçu maîtrisable dans l’application principale plutôt que de traiter la planification du système comme une minuterie de l’application.

Diagnostiquer selon les symptômes plutôt que réinstaller sans cesse

Observation À vérifier en priorité
La valeur relue par l’application principale est déjà incorrecte Le moment de l’écriture, le nom de la suite et la gestion des erreurs
L’application principale lit la bonne valeur, mais l’extension lit encore l’ancienne La configuration App Group des deux targets et la version de l’enregistrement
L’extension lit la nouvelle valeur, mais les entrées restent anciennes Les paramètres transmis au fournisseur et la construction de la chronologie
Les nouvelles entrées sont correctes, mais l’écran reste inchangé Le type de widget, les valeurs utilisées par la vue et le moment où le système effectue réellement l’affichage

Avant de chercher la cause, notez le commit du projet, la version de l’environnement d’exécution, l’identifiant de l’appareil et le numéro de révision de l’enregistrement partagé. Ces éléments sont plus utiles qu’une capture d’écran montrant simplement que « rien n’a changé ». N’effacez pas tout le simulateur après chaque échec : vous supprimeriez aussi les données partagées qui permettent de reproduire le problème. Si une réinitialisation s’impose, conservez d’abord les résultats des tests et les journaux nécessaires expurgés de leurs données sensibles, puis choisissez explicitement entre effacer les données de l’application et recréer le simulateur.

Transmettre les critères de validation au pipeline

Organisez l’automatisation en deux niveaux. Le premier exécute les tests de la fonction pure et détecte les régressions portant sur l’ordre des entrées, la valeur zéro ou le numéro de révision. Le second vérifie, sur un simulateur déterminé, que l’application et l’extension sont installées et lisent le même enregistrement, puis contrôle la disposition manuellement ou au moyen d’un aperçu maîtrisable. Le rafraîchissement réel du widget reste un point d’observation distinct : ne le mêlez pas aux assertions déterministes du premier niveau.

Pour finir, vérifiez trois points : les deux targets utilisent-ils le même groupe partagé ? Les nouvelles données portent-elles un numéro de révision traçable sans divulguer leur contenu ? Les tests reçoivent-ils l’heure en entrée au lieu d’attendre que de vraies minutes s’écoulent ? Si ces conditions sont remplies, la prochaine fois qu’un ancien affichage persistera, vous pourrez remonter successivement les données, les entrées et l’affichage, sans multiplier les demandes de rafraîchissement pour deviner la cause.

Questions fréquentes

Pourquoi le widget ne change-t-il pas immédiatement après une demande d’actualisation ?

La demande ne garantit pas une exécution immédiate. Vérifiez d’abord la nouvelle valeur dans le conteneur partagé et sa lecture par le fournisseur, puis observez la chronologie réellement produite.

Faut-il attendre une actualisation du simulateur pour tester la chronologie ?

Non. Injectez l’heure et les données dans une fonction pure pour vérifier les entrées. Réservez le simulateur à l’installation, au transfert des données et à l’affichage final.

Apple Silicon dédié

Intégrez un Mac cloud à configuration fixe à votre workflow

Louez des nœuds physiques dédiés à la journée, à la semaine, au mois ou au trimestre pour exécuter vos builds, automatisations et tâches d’inférence MLX sur des ressources matérielles fixes.

Voir les configurations disponibles