Une personne enregistre un document dans une application, mais ne parvient pas à l’ouvrir depuis les résultats de recherche du système. Sur la machine de développement, le test réussit ; sur un Mac cloud, le test automatisé échoue parfois : le rappel d’écriture dans l’index a signalé un succès, mais la recherche ne renvoie toujours aucun résultat. Pour diagnostiquer ce problème, il faut distinguer trois étapes du cycle de vie de l’index : « écriture réussie », « élément trouvable » et « élément absent après suppression ». Chacune doit être vérifiée séparément.
Définir le périmètre du test
Dans Core Spotlight, CSSearchableIndex sert à ajouter et à supprimer les éléments recherchables fournis par l’application. Le rappel d’écriture indique le résultat de cette opération, pas que les nouvelles données sont déjà visibles par une requête de recherche. Le test doit donc consigner au minimum l’identifiant de l’élément, le terme recherché, le résultat de l’écriture, le délai avant la première correspondance et le résultat de la recherche après suppression.
Exécutez le test dans une cible XCTest hébergée par l’application, et non uniquement dans une cible de tests logiques qui ne charge pas son environnement. Utilisez un domainIdentifier réservé aux tests. À chaque exécution, générez un nouveau uniqueIdentifier et un nouveau titre pour éviter qu’un élément laissé par une exécution précédente ne produise un faux positif. Validez d’abord le parcours avec un seul élément, puis passez à l’écriture en lot. N’utilisez pas un document métier fixe comme échantillon : si une personne le modifie ou le supprime, le résultat du test changera.
Un rappel d’écriture réussi ne valide que l’étape d’ajout. L’étape de recherche exige une requête ; celle de suppression exige une nouvelle requête. Conservez le résultat de chacune.
Vérifier l’écriture et la suppression avec XCTest
Le test suivant montre comment intégrer des opérations asynchrones à XCTest. Il supprime d’abord tout élément éventuellement présent sous l’identifiant choisi, écrit ensuite un élément portant un titre unique, puis supprime cet identifiant. L’exemple vérifie les rappels des opérations ; il ne démontre pas à lui seul que l’élément est visible en recherche. Ajoutez la vérification décrite dans la section suivante.
import CoreSpotlight
import XCTest
final class SpotlightLifecycleTests: XCTestCase {
func testWriteAndRemove() {
let index = CSSearchableIndex.default()
let id = "smoke-\(UUID().uuidString)"
let title = "SpotlightSmoke-\(id)"
let attributes = CSSearchableItemAttributeSet(contentType: .text)
attributes.title = title
let item = CSSearchableItem(
uniqueIdentifier: id,
domainIdentifier: "spotlight-smoke-tests",
attributeSet: attributes
)
func complete(
_ name: String,
_ operation: (@escaping (Error?) -> Void) -> Void
) {
let done = expectation(description: name)
operation { error in
XCTAssertNil(error, "\(name): \(String(describing: error))")
done.fulfill()
}
wait(for: [done], timeout: 10)
}
complete("remove previous item") { finish in
index.deleteSearchableItems(
withIdentifiers: [id],
completionHandler: finish
)
}
complete("index item") { finish in
index.indexSearchableItems(
[item],
completionHandler: finish
)
}
complete("remove indexed item") { finish in
index.deleteSearchableItems(
withIdentifiers: [id],
completionHandler: finish
)
}
}
}
Dans un projet réel, placez la suppression dans la procédure de nettoyage du test : si une assertion échoue après l’écriture, le test doit quand même tenter de retirer l’élément ajouté. Ne passez pas sous silence un échec du nettoyage ; signalez-le avec l’identifiant de l’élément, faute de quoi les exécutions suivantes risquent d’être perturbées par des données résiduelles.
Accorder un délai borné à la recherche
Dans la cible de test, interrogez CSSearchQuery avec le titre ou le terme de recherche unique généré pour cette exécution, puis vérifiez le uniqueIdentifier des éléments renvoyés. Ne vous contentez pas d’affirmer qu’« au moins un résultat » existe : des données métier portant le même nom pourraient faire passer le mauvais élément. Lancez la requête après le rappel d’écriture. Si l’élément est absent, attendez brièvement, puis créez une nouvelle requête ; ne faites échouer le test que si l’élément reste introuvable à l’échéance définie. Après le rappel de suppression, procédez de la même manière jusqu’à ce que l’identifiant n’apparaisse plus.
| Étape | Critère de réussite | À consigner en priorité en cas d’échec |
|---|---|---|
| Écriture | Rappel sans erreur | Identifiant, erreur, état du simulateur |
| Recherche | Identifiant de cette exécution trouvé avant l’échéance | Terme recherché, erreur de requête, durée |
| Suppression | Identifiant absent des résultats avant l’échéance | Erreur de suppression, résultats de la dernière requête |
L’échéance est un budget de test, pas une garantie de performance du service. Observez d’abord plusieurs exécutions sur une configuration de simulateur fixe, puis définissez une limite adaptée ; n’allongez pas indéfiniment l’attente à cause d’une seule requête lente. Prévoyez aussi des conditions d’arrêt distinctes pour le rappel de requête et la boucle d’interrogation, afin qu’un blocage ne monopolise pas tout le pipeline.
Stabiliser l’exécution sur un Mac cloud
Fixez le projet, le Scheme et le simulateur utilisés pour ce test, et vérifiez que le Scheme est partagé. Avant la première exécution, utilisez xcodebuild -showdestinations pour voir les destinations disponibles, puis inscrivez l’identifiant de l’appareil choisi dans la configuration d’exécution. Ne laissez pas l’outil sélectionner automatiquement le « premier » simulateur. Par exemple :
xcodebuild test \
-project Example.xcodeproj \
-scheme Example \
-destination 'platform=iOS Simulator,id=SIMULATOR_UDID' \
-only-testing:ExampleTests/SpotlightLifecycleTests \
-resultBundlePath "$PWD/spotlight-result.xcresult"
Remplacez SIMULATOR_UDID et le nom du projet par leurs valeurs réelles ; le chemin du paquet de résultats ne doit pas exister au début de chaque exécution. Un test d’indexation ne nécessite généralement pas d’effacer tout le simulateur. Nettoyez en priorité le domaine de test et l’identifiant de l’exécution : cela réduit la durée du test sans supprimer l’état dont d’autres tests ont besoin. Si une réinitialisation de l’appareil est indispensable, effectuez-la dans une tâche distincte pour éviter de partager le même ensemble d’appareils avec des tests parallèles.
Diagnostiquer les échecs intermittents et conclure
En cas d’échec, examinez d’abord l’étape concernée au lieu de relancer immédiatement le test. Si le rappel d’écriture renvoie une erreur, vérifiez l’hôte de test, les attributs de l’élément et l’objet d’erreur. Si l’écriture réussit mais que la recherche expire, vérifiez que le terme recherché figure bien dans un attribut recherchable, que la requête cible l’identifiant de cette exécution et qu’aucune autre tâche ne nettoie le même domaine de test. Si l’élément reste trouvable après sa suppression, consignez l’identifiant du dernier élément trouvé et confirmez que l’appel de suppression visait le même uniqueIdentifier.
Vérifiez enfin l’organisation des exécutions parallèles : les tests du cycle de vie de l’index d’une même application ne doivent pas s’exécuter en parallèle sur le même simulateur s’ils partagent une opération de nettoyage du domaine. En conservant l’identifiant de chaque exécution, les résultats des trois étapes et le paquet de résultats du test, vous pourrez distinguer une erreur d’opération, un délai de synchronisation de la recherche et une contamination de l’état du test. Le parcours de recherche de l’utilisateur n’est validé que lorsque l’écriture, la recherche et la suppression réussissent toutes les trois.
Questions fréquentes
Pourquoi un élément reste-t-il introuvable après la confirmation de son ajout ?
La confirmation termine l’opération d’ajout, pas nécessairement la synchronisation de la recherche. Interrogez un terme unique avec une limite de temps.
Comment éviter qu’une exécution précédente fausse le résultat ?
Créez un identifiant et un terme uniques par exécution, supprimez l’élément avant et après le test, puis consignez l’identifiant en cas d’échec.
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.