Die am schwierigsten reproduzierbaren Fehler bei In-App-Käufen sind meist keine nicht reagierenden Schaltflächen, sondern über Testfälle hinweg übernommene Transaktionszustände: Der erste Test schließt einen Kauf ab, während der zweite eigentlich die Oberfläche ohne Kauf prüfen soll, aber sofort einen bereits freigeschalteten Zustand vorfindet. Bei langfristig ausgeführten Regressionstests auf einem Cloud-Mac müssen Simulator, StoreKit-Sitzung und persistente App-Daten gemeinsam isoliert werden. Andernfalls werden erfolgreiche Einzeltests und fehlschlagende Gesamtläufe zur Regel.
Testgrenzen in drei Ebenen aufteilen
Die erste Ebene bildet eine reine Zustandsmaschine für die Geschäftslogik. Sie erhält die Zustände „nicht gekauft“, „in Bearbeitung“, „gekauft“, „erstattet“ und „abgelaufen“ als Eingabe und prüft Funktionsberechtigungen sowie UI-Zustände, ohne echte Transaktionen zu starten. Die zweite Ebene verwendet StoreKit Testing, um Produktabfragen, Kauf-Callbacks, die Transaktionsüberwachung und Änderungen an Abonnements zu testen. Erst die dritte Ebene dient der kontrollierten Abnahme vor der Veröffentlichung und überprüft serverseitige Benachrichtigungen sowie die produktive Produktkonfiguration.
Mit dieser Aufteilung kann der Großteil der Regressionstests lokal ausgeführt werden. Zugleich lässt sich klar erkennen, ob ein Fehler in der Geschäftslogik, in der clientseitigen Transaktionsanbindung oder in einer externen Konfiguration liegt. Nicht jeder UI-Test sollte mit einem Klick auf die Kaufschaltfläche beginnen. Tests der Zustandsmaschine sollten den größten Anteil ausmachen; StoreKit-Integrationstests sollten nur die kritischen Pfade abdecken.
StoreKit Testing stellt eine kontrollierbare Transaktionsumgebung bereit, ersetzt aber nicht die produktive Transaktionskette. Ein erfolgreicher Test bestätigt, dass sich der Client bei den vorgegebenen Ereignissen korrekt verhält. Er bedeutet nicht, dass die externe Konfiguration bereits abgenommen wurde.
Einen überprüfbaren Produktkatalog anlegen
Erstellen Sie im Projekt die Datei StoreKit/Local.storekit. Die Produktkennungen müssen exakt mit den im Code verwendeten Kennungen übereinstimmen. Es empfiehlt sich, die Kennungen zentral zu definieren, statt sie über Views und Tests zu verteilen:
enum ProductID {
static let proMonthly = "com.example.app.pro.monthly"
static let proYearly = "com.example.app.pro.yearly"
}
Die Konfigurationsdatei sollte in die Versionsverwaltung aufgenommen werden. Änderungen an Testpreisen, Anzeigenamen und Abonnementzeiträumen müssen ebenfalls ein Code-Review durchlaufen. Ein häufiger Fehler besteht darin, eine alte Konfiguration zu kopieren und nur den Anzeigenamen zu ändern, nicht aber die Produktkennung. Die Produktabfrage liefert dann ein leeres Array zurück.
Legen Sie für die Tests ein separates Scheme wie StoreKitRegression an und wählen Sie in den Run- und Test-Optionen des Schemes dieselbe .storekit-Datei aus. Verlassen Sie sich nicht auf persönliche Schemes einzelner Entwickler. Nur ein freigegebenes Scheme kann von xcodebuild gefunden werden.
| Prüfpunkt | Erwartetes Ergebnis |
|---|---|
| Produktkennung | Konfigurationsdatei, Code und Assertions stimmen vollständig überein |
| Scheme | Als Shared markiert und in das Repository eingecheckt |
| Abonnementgruppe | Upgrade- und Downgrade-Beziehungen innerhalb derselben Gruppe sind eindeutig |
| Lokalisierung | Test-Assertions hängen nicht von leicht veränderlichen Anzeigetexten ab |
Jeden Testfall mit einer sauberen Sitzung beginnen
Erstellen Sie mit StoreKitTest eine Testsitzung. Deaktivieren Sie in setUp die Systemdialoge und löschen Sie vorhandene Transaktionen. Nach Abschluss einer Testmethode müssen außerdem die von der App selbst gespeicherten Berechtigungsdaten bereinigt werden.
import XCTest
import StoreKitTest
final class PurchaseRegressionTests: XCTestCase {
private var session: SKTestSession!
override func setUpWithError() throws {
session = try SKTestSession(
configurationFileNamed: "Local.storekit"
)
session.disableDialogs = true
session.clearTransactions()
UserDefaults.standard.removeObject(forKey: "cachedEntitlements")
}
func testMonthlyPurchaseUnlocksPro() async throws {
try await session.buyProduct(
identifier: ProductID.proMonthly
)
let unlocked = await EntitlementStore.shared.refresh()
XCTAssertTrue(unlocked)
}
}
Der Transaktions-Listener muss vor dem Auslösen des Kaufs gestartet werden. Wird ein Kauf sehr schnell abgeschlossen, kann die Listener-Task die Aktualisierung andernfalls verpassen, sodass der Test sporadisch in ein Timeout läuft. Erstattungen, Widerrufe und abgelaufene Abonnements sollten ebenfalls aktiv über die Sitzung ausgelöst werden. Anschließend ist das Ergebnis der aktualisierten Berechtigungen zu prüfen, statt lediglich zu kontrollieren, ob die Kauf-API erfolgreich zurückgekehrt ist.
Race Conditions nicht mit festen Wartezeiten verdecken
Task.sleep verschiebt einen Fehler lediglich und beweist nicht, dass der Zustand aktualisiert wurde. Robuster ist es, wenn der Berechtigungsspeicher einen beobachtbaren Zustand bereitstellt und der Test mit einem kurzen Timeout auf eine eindeutige Bedingung wartet. Timeout-Protokolle sollten mindestens Produktkennung, aktuelle Berechtigungen, Anzahl nicht abgeschlossener Transaktionen und Testnamen enthalten. Token oder vollständige Zugangsdaten dürfen dabei nicht ausgegeben werden.
Einen festen Einstiegspunkt für die Befehlszeile verwenden
Prüfen Sie zunächst, welche Simulatorgeräte auf dem Cloud-Mac installiert sind, und konfigurieren Sie anschließend einen festen Gerätenamen für die CI. Wenn das Image andere Gerätenamen verwendet, müssen die Pipeline-Parameter angepasst werden. Das Skript sollte nicht unbemerkt ein beliebiges Gerät auswählen.
xcrun simctl list devices available
xcodebuild test \
-workspace Example.xcworkspace \
-scheme StoreKitRegression \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-resultBundlePath Artifacts/StoreKitTests.xcresult \
CODE_SIGNING_ALLOWED=NO
Wenn ausschließlich Simulatortests ausgeführt werden, reduziert die deaktivierte Codesignierung Fehler, die nichts mit der Transaktionslogik zu tun haben. Das Ergebnisverzeichnis muss vor dem Lauf geleert oder anhand der Jobnummer aufgeteilt werden. Eine bereits vorhandene xcresult-Datei führt andernfalls unmittelbar zu einem Befehlsfehler.
StoreKit-Integrationstests sind standardmäßig stabiler, wenn sie seriell ausgeführt werden. Bei paralleler Ausführung benötigt jede Ausführungseinheit einen eigenen Simulator, eigene Derived Data und ein separates Ergebnisverzeichnis. Wenn mehrere Prozesse gleichzeitig denselben Simulator verwenden, verunreinigen sich Transaktionswarteschlange und App-Daten gegenseitig.
Fehler als verwertbare Belege erfassen
Mindestens die sechs Pfade Erstkauf, Abbruch durch den Benutzer, ausstehende Transaktion, Erstattung, Abonnementverlängerung und Ablauf des Abonnements müssen abgedeckt werden. Für jeden Pfad sind drei Ebenen zu prüfen: der von StoreKit zurückgegebene Zustand, der Zustand des Berechtigungsspeichers und der endgültige UI-Zustand. Wer nur eine Änderung der Schaltflächenbeschriftung prüft, übersieht möglicherweise, dass die Berechtigungen im Hintergrund nicht aktualisiert wurden.
Vor dem Einchecken empfiehlt sich folgende Prüfung:
- Die
.storekit-Datei und das freigegebene Scheme befinden sich in der Versionsverwaltung. - Vor jedem Testfall werden Transaktionen und der lokale Berechtigungs-Cache gelöscht.
- Der Transaktions-Listener wird vor dem Kaufvorgang gestartet.
- Die Tests hängen weder von festen Wartezeiten noch von der Ausführungsreihenfolge ab.
- Jeder parallele Job verwendet einen eigenen Simulator und ein separates Artefaktverzeichnis.
- Bei Fehlern werden
xcresult, Testprotokolle und UI-Anhänge aufbewahrt. - Serverseitige Benachrichtigungen und die Produktkonfiguration werden vor der produktiven Veröffentlichung separat geprüft.
Wenn Sie solche Aufgaben auf HireVM ausführen, prüfen Sie zunächst in der Konsole die aktuell verfügbaren Konfigurationen und planen Sie die Simulatoren anschließend anhand der Anzahl der Test-Shards. In-App-Kauftests reagieren meist stärker auf eine unzureichende Zustandsisolation als auf die Dauer eines einzelnen Build-Vorgangs. Sorgen Sie zuerst dafür, dass eine serielle Pipeline wiederholt erfolgreich durchläuft, und erhöhen Sie erst danach die Parallelität. So bleibt der Aufwand für die Fehlersuche deutlich geringer.
Häufig gestellte Fragen
Ersetzt StoreKit Testing die vollständige Prüfung realer Käufe?
Nein. Produktzuordnung, Zustandswechsel, Oberfläche und Fehlerpfade lassen sich prüfen. Serverbenachrichtigungen, reale Produktkonfiguration und die vollständige Transaktionskette benötigen eine separate Abnahme.
Warum schlägt ein Test nur innerhalb der gesamten Suite fehl?
Häufig bleiben Transaktionen, Verlängerungen oder lokale App-Daten aus einem vorherigen Test erhalten. Sitzung und App-Zustand müssen vor jedem Fall bereinigt werden.
Dürfen StoreKit-Tests parallel laufen?
Ja, wenn jeder Worker einen eigenen Simulator und ein eigenes Ergebnisverzeichnis erhält. Mehrere Prozesse sollten niemals dieselbe StoreKit-Sitzung gemeinsam verwenden.
Führe deine nächste Remote-Mac-Aufgabe auf einem dedizierten Knoten aus
Wähle je nach Projektumfang aus zwei verfügbaren Konfigurationen, fünf Standorten sowie Tages-, Wochen-, Monats- oder Quartalslaufzeiten. Vor der Bestellung kannst du Konfiguration und USD-Betrag vollständig prüfen.