Engineering-Praxis

Core-Spotlight-Einträge auf einem Cloud-Mac anlegen, finden und entfernen

Core-Spotlight-Einträge auf einem Cloud-Mac anlegen, finden und entfernen

Ein Nutzer speichert ein Dokument in der App, kann es aber aus den System-Suchergebnissen nicht öffnen. Auf dem Entwicklungsrechner funktioniert der erneute Versuch, während der automatisierte Test auf einem Cloud-Mac gelegentlich fehlschlägt: Der Callback für die Indexierung meldet Erfolg, doch die Suche liefert noch keinen Treffer. Um solche Probleme einzugrenzen, dürfen „erfolgreich übermittelt“, „über die Suche auffindbar“ und „nach dem Löschen nicht mehr auffindbar“ nicht zu einer einzigen Assertion zusammengefasst werden. Diese drei Phasen des Indexlebenszyklus müssen getrennt geprüft werden.

Zuerst die Testgrenzen festlegen

CSSearchableIndex von Core Spotlight übermittelt und entfernt die durch die App bereitgestellten durchsuchbaren Einträge. Der Callback beim Schreiben meldet das Ergebnis dieser Operation – nicht, dass eine Suchabfrage die neuen Daten bereits lesen kann. Der Test sollte daher mindestens die Eintragskennung, den Suchbegriff, das Schreibergebnis, den Zeitpunkt des ersten Treffers und das Abfrageergebnis nach dem Löschen erfassen.

Führe den Test in einem XCTest-Target mit App-Host aus, statt ausschließlich in einem Logiktest-Target, das die App-Umgebung nicht lädt. Die Testeinträge verwenden einen eigenen domainIdentifier; jeder Durchlauf erzeugt einen neuen uniqueIdentifier und einen neuen Titel. So können zurückgebliebene Einträge aus früheren Durchläufen keine falschen Treffer verursachen. Prüfe den Ablauf zunächst mit einem Eintrag und erweitere ihn erst danach auf mehrere Einträge. Ein festes Geschäftsdokument eignet sich nicht als Testdatenbasis: Wird es von einem Nutzer geändert oder gelöscht, ändert sich auch das Testergebnis.

Ein erfolgreicher Schreib-Callback bestätigt nur die Übermittlungsphase. Für die Suche muss eine Abfrage ausgeführt werden, nach dem Entfernen eine weitere. Die Ergebnisse aller drei Phasen sind getrennt festzuhalten.

Schreiben und Entfernen mit XCTest prüfen

Der folgende Test zeigt, wie sich asynchrone Operationen in XCTest einbinden lassen. Er entfernt zunächst einen möglicherweise verbliebenen Eintrag anhand seiner Kennung, schreibt dann einen Datensatz mit eindeutigem Titel und löscht schließlich diese Kennung. Das Beispiel prüft die Callbacks der Operationen; es belegt für sich genommen nicht, dass der Eintrag bereits über die Suche auffindbar ist. Die Such-Assertion muss wie im nächsten Abschnitt beschrieben ergänzt werden.

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
            )
        }
    }
}

In einem echten Projekt gehört das Löschen in den Bereinigungspfad des Tests: Schlägt eine Assertion nach dem Schreiben fehl, muss trotzdem versucht werden, den bereits übermittelten Eintrag zu entfernen. Ein Fehler bei der Bereinigung darf nicht stillschweigend übergangen werden. Melde ihn zusammen mit der Eintragskennung, damit zurückgebliebene Daten spätere Durchläufe nicht unbemerkt beeinflussen.

Frage im Test-Target mit CSSearchQuery nach dem eindeutigen Titel oder Suchbegriff des aktuellen Durchlaufs und prüfe den uniqueIdentifier der zurückgegebenen Einträge. Die Bedingung „mindestens ein Treffer“ genügt nicht: Gleichnamige Geschäftsdaten könnten einen falschen Eintrag durch den Test bringen. Starte die Abfrage, sobald der Schreib-Callback zurückkehrt. Bleibt der Treffer aus, warte kurz und erstelle die Abfrage erneut; der Test schlägt erst fehl, wenn die festgelegte Frist ohne Treffer abläuft. Nach dem Lösch-Callback wird auf dieselbe Weise wiederholt abgefragt, bis die Kennung nicht mehr erscheint.

Phase Erfolgskriterium Bei einem Fehler zuerst erfassen
Schreiben Callback ohne Fehler Kennung, Fehler, Simulatorzustand
Suche Kennung dieses Durchlaufs vor Fristablauf gefunden Suchbegriff, Abfragefehler, Dauer
Löschen Kennung wird vor Fristablauf nicht mehr zurückgegeben Löschfehler, Ergebnis der letzten Abfrage

Die Frist ist ein Zeitbudget für den Test, keine Leistungszusage für den Dienst. Beobachte zunächst mehrere Durchläufe mit einer festen Simulatorkonfiguration und lege dann eine sinnvolle Obergrenze fest; verlängere die Wartezeit nicht unbegrenzt wegen einer einzelnen langsamen Abfrage. Auch der Abfrage-Callback und die Polling-Schleife brauchen jeweils eine Abbruchbedingung, damit ein Hänger nicht die gesamte Pipeline blockiert.

Den Testaufruf auf dem Cloud-Mac festlegen

Lege für diesen Test Projekt, Scheme und Simulatorziel fest und vergewissere dich, dass das Scheme freigegeben ist. Prüfe vor dem ersten Lauf mit xcodebuild -showdestinations, welche Ziele aktuell verfügbar sind. Hinterlege dann die Kennung des ausgewählten Geräts in der Ausführungskonfiguration, statt dich auf die automatische Auswahl des „ersten“ Simulators zu verlassen. Zum Beispiel:

xcodebuild test \
  -project Example.xcodeproj \
  -scheme Example \
  -destination 'platform=iOS Simulator,id=SIMULATOR_UDID' \
  -only-testing:ExampleTests/SpotlightLifecycleTests \
  -resultBundlePath "$PWD/spotlight-result.xcresult"

Ersetze SIMULATOR_UDID und den Projektnamen durch die tatsächlichen Werte; der Pfad für das Ergebnis-Bundle darf vor jedem Lauf noch nicht existieren. Für Indextests muss üblicherweise nicht der gesamte Simulator zurückgesetzt werden. Bereinige vorzugsweise die Testdomäne und die Kennung des aktuellen Durchlaufs. Das spart Zeit und erhält Zustände, die andere Tests benötigen. Falls ein Zurücksetzen des Geräts unvermeidlich ist, führe es in einem separaten Job aus, damit parallel laufende Tests nicht denselben Gerätesatz verwenden.

Gelegentliche Fehler eingrenzen und den Test abschließen

Prüfe bei einem Fehlschlag zuerst die betroffene Phase, statt den Test sofort erneut zu starten. Meldet der Schreib-Callback einen Fehler, untersuche den Test-Host, die Eintragsattribute und das Fehlerobjekt. War das Schreiben erfolgreich, läuft aber die Suche in ein Timeout, prüfe, ob der Suchbegriff tatsächlich in einem durchsuchbaren Attribut steht, ob die Abfrage die Kennung dieses Durchlaufs verwendet und ob ein anderer Job gerade dieselbe Testdomäne bereinigt. Bleibt ein Eintrag nach dem Löschen auffindbar, erfasse die Kennung des letzten Treffers und kontrolliere, ob der Löschaufruf denselben uniqueIdentifier verwendet hat.

Prüfe abschließend die Parallelisierung: Tests zum Indexlebenszyklus derselben App dürfen nicht parallel auf demselben Simulator laufen, wenn sie eine gemeinsame Bereinigung der Domäne verwenden. Bewahrst du für jeden Durchlauf die Kennung, die Ergebnisse aller drei Phasen und das Testergebnis-Bundle auf, lassen sich Operationsfehler, Verzögerungen bei der Suchsynchronisierung und Zustandsverschmutzung durch den Test selbst unterscheiden. Erst wenn Schreiben, Suchen und Entfernen erfolgreich waren, ist dieser Suchpfad aus Nutzersicht vollständig geprüft.

Häufig gestellte Fragen

Warum ist ein Eintrag nach erfolgreicher Indexierung nicht sofort auffindbar?

Der Rückruf bestätigt den Indexierungsvorgang, nicht die sofortige Sichtbarkeit in der Suche. Fragen Sie einen eindeutigen Suchbegriff bis zu einer festen Frist erneut ab.

Wie verhindere ich Verwechslungen zwischen Testläufen?

Erzeugen Sie pro Lauf eine eigene Kennung samt Suchbegriff, löschen Sie den Eintrag vor und nach dem Test und protokollieren Sie die Kennung bei Fehlern.

Exklusives Apple Silicon

Binde einen Cloud-Mac mit fester Konfiguration in deinen Workflow ein

Miete dedizierte physische Knoten tage-, wochen-, monats- oder quartalsweise und nutze feste Hardwareressourcen für Builds, Automatisierung und MLX-Inferenz.

Verfügbare Konfigurationen ansehen