Engineering-Dossier

Reproduzierbare In-App-Kaufregressionen mit StoreKit testen

Reproduzierbare In-App-Kaufregressionen mit StoreKit testen

Bei Zustandsautomaten für In-App-Käufe ist meist nicht der erfolgreiche Kauf am schwierigsten zu reproduzieren, sondern Abbrüche, ausstehende Vorgänge, Erstattungen und nicht abgeschlossene frühere Transaktionen. Werden diese Pfade vollständig einer externen Transaktionsumgebung überlassen, beeinflussen Netzwerkbedingungen, Produktkonfiguration und der Zustand des Testkontos die Ergebnisse. Zuverlässiger ist es, die Produkte zunächst auf einem Cloud Mac über eine StoreKit-Konfigurationsdatei festzulegen und anschließend die Transaktionsbedingungen mit SKTestSession zu steuern. So wird die Clientlogik zu einem reproduzierbaren Regressionstest.

Grenzen von Offline-Tests klar definieren

StoreKit-Konfigurationen eignen sich dazu, die Zuordnung von Produktkennungen, Übergänge zwischen Kaufzuständen, die Aktualisierung von Berechtigungen, Fehlermeldungen und den Abschluss von Transaktionen zu prüfen. Ein Zugriff auf externe Zahlungswege ist nicht erforderlich, und Fehler lassen sich leichter reproduzieren.

Sie können jedoch weder belegen, dass Produkte in der Produktionsumgebung korrekt konfiguriert sind, noch serverseitige Benachrichtigungen, die Validierung echter Belege oder den endgültigen Zahlungsdialog abdecken. Es empfiehlt sich, die Tests in drei Ebenen aufzuteilen:

Ebene Hauptziel Ausführungshäufigkeit
Unit-Tests Berechtigungsberechnung, Zustandszuordnung Bei jedem Commit
StoreKit-Integrationstests Kauf, Abbruch, Erstattung, nicht abgeschlossene Transaktionen Bei jedem Merge
Abnahme in externer Umgebung Produktkonfiguration, Benachrichtigungs- und Belegpfade Vor der Veröffentlichung

Offline-Tests verkürzen die Feedbackschleife. Sie dienen nicht dazu, sämtliche Transaktionsrisiken hinter einem grünen Häkchen zu verbergen.

Produkte und Testeinstieg festlegen

Erstellen Sie in Xcode eine StoreKit Configuration File und behalten Sie darin nur die für den Test benötigten Produkte. Die Produktkennungen müssen mit den Konstanten im Code übereinstimmen. Preise dienen ausschließlich UI-Tests; die Geschäftslogik darf Berechtigungen nicht anhand lokalisierter Preiszeichenfolgen bestimmen.

Fügen Sie die Konfigurationsdatei dem Test-Scheme hinzu und erstellen Sie einen separaten Testplan, beispielsweise StoreKitRegression.xctestplan. Verwenden Sie nicht das Scheme, das Entwickler für ihre tägliche Arbeit ausführen. Eine einzelne manuelle Änderung könnte sonst unbemerkt die CI-Bedingungen verändern.

Das Test-Target kann die Sitzung in setUpWithError() neu erstellen:

import StoreKitTest
import XCTest

final class PurchaseRegressionTests: XCTestCase {
    private var session: SKTestSession!

    override func setUpWithError() throws {
        session = try SKTestSession(configurationFileNamed: "Products")
        session.disableDialogs = true
        session.clearTransactions()
        session.failTransactionsEnabled = false
    }

    override func tearDownWithError() throws {
        session.clearTransactions()
        session = nil
    }
}

Der Name der Konfigurationsdatei wird ohne Dateierweiterung angegeben. Schlägt die Initialisierung fehl, prüfen Sie zuerst, ob die Datei dem Test-Target angehört und ob das Scheme dieselbe Konfiguration verwendet.

Transaktionspfade in separate Testfälle aufteilen

Testen Sie Erfolg, Erstattung und Abbruch nicht nacheinander in einem langen Testfall. Je mehr Zustände beteiligt sind, desto schwieriger lässt sich nach einem Fehler feststellen, in welchem Schritt der Zustand verunreinigt wurde. Mindestens die folgenden Fälle sollten getrennt werden:

Regulärer Kauf und Wiederherstellung

Prüfen Sie, ob die Berechtigungen unmittelbar nach dem Kauf aktualisiert werden und auch nach dem erneuten Erstellen der Geschäftslogikobjekte weiterhin als gültig gelesen werden können. Stellen Sie vor dem Testende sicher, dass die App die Transaktion verarbeitet und abgeschlossen hat. Andernfalls könnte der nächste Testfall erneut ein altes Update erhalten.

Abbruch und Fehlerinjektion

Ein Abbruch darf weder als „Zahlung fehlgeschlagen“ angezeigt werden noch einen entsperrten Zustand speichern. Bei Netzwerkfehlern oder allgemeinen Transaktionsfehlern muss hingegen eine Wiederholungsmöglichkeit bestehen bleiben. Nachdem ein Fehler über Sitzungseigenschaften injiziert wurde, müssen diese noch vor Ende des aktuellen Tests auf ihre Standardwerte zurückgesetzt werden. Verlassen Sie sich dabei nicht auf die Bereinigung durch den nächsten Testfall.

Erstattung und nicht abgeschlossene Transaktionen

Nach einer Erstattung muss die zugehörige Berechtigung entzogen werden, ohne andere weiterhin gültige Produkte versehentlich zu entfernen. Bei nicht abgeschlossenen Transaktionen sollte geprüft werden, ob die App die Verarbeitung nach einem Neustart fortsetzen kann. Es genügt nicht, lediglich einmalig einen Button-Callback zu kontrollieren.

Tests, die den Zustand desselben Produkts betreffen, sollten möglichst seriell ausgeführt werden. Teilen sich parallele Tests einen Simulator und dieselbe Konfiguration, können sie gegenseitig ihre Transaktionen löschen. Dadurch entstehen sporadische Fehler, die ausschließlich in der CI auftreten.

Ausführungsbedingungen auf der Kommandozeile festlegen

Prüfen Sie zunächst die verfügbaren Simulatoren und hinterlegen Sie anschließend Gerätename, Systemversion und Xcode-Pfad als CI-Variablen. Überlassen Sie xcodebuild nicht die automatische Auswahl eines beliebigen Ziels.

set -euo pipefail

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
RESULT_DIR="$PWD/TestResults"
rm -rf "$RESULT_DIR"
mkdir -p "$RESULT_DIR"

xcodebuild test \
  -workspace App.xcworkspace \
  -scheme App-StoreKitTests \
  -testPlan StoreKitRegression \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath "$RESULT_DIR/StoreKit.xcresult"

Wenn auf OakVM mehrere Pipelines gleichzeitig laufen, sollte jeder Auftrag ein eigenes Arbeitsverzeichnis und einen eigenen Simulator erhalten. DerivedData, Ergebnisverzeichnisse und bereits gestartete Geräte dürfen nicht gemeinsam verwendet werden. Vor dem Start kann xcrun simctl shutdown all ausgeführt werden. Da der Befehl jedoch auch andere Aufträge auf demselben Knoten beeinflusst, eignet er sich nur für Ausführungsmodelle, bei denen ein einzelner Auftrag den Knoten exklusiv nutzt.

Nachweise archivieren und Fehlerschwellen definieren

Nach einem fehlgeschlagenen Test reicht es meist nicht aus, nur die letzten Dutzend Zeilen der Konsolenausgabe zu speichern. Archiviert werden sollten xcresult, der Commit-Hash, die Prüfsumme der StoreKit-Konfigurationsdatei, die Xcode-Version und die Version der Simulator-Laufzeit. Änderungen an der Konfigurationsdatei müssen ebenfalls in die Codeüberprüfung einfließen, damit Produktkennungen nicht versehentlich entfernt werden.

Zunächst kann eine Zusammenfassung der Umgebung aufgezeichnet werden:

xcodebuild -version
xcrun simctl list runtimes
shasum -a 256 Tests/StoreKit/Products.storekit

Die Prüfliste sollte folgende Punkte enthalten:

  • Erstellt und bereinigt jeder Test seine eigene SKTestSession?
  • Sind automatische Dialoge deaktiviert, damit unbeaufsichtigte Aufträge nicht blockiert werden?
  • Werden bei Abbruch, Fehler und Erstattung sowohl UI-Zustand als auch Berechtigungszustand separat geprüft?
  • Hängt ein Test von der Ausführungsreihenfolge oder einer vorherigen Transaktion ab?
  • Wird xcresult auch nach einem Fehler hochgeladen?
  • Vermeiden die Protokolle vertrauliche Zugangsdaten und vollständige Transaktionsdaten?

Sind diese Bedingungen festgelegt, hängen Regressionstests für In-App-Käufe nicht mehr von manuellen Klicks ab. Die externe Umgebung übernimmt nur noch die Prüfungen, die tatsächlich dort stattfinden müssen. Bei alltäglichen Commits schützen schnelle, isolierte und nachvollziehbare Tests den Zustandsautomaten des Clients.

Häufig gestellte Fragen

Ersetzen Offline-Tests mit StoreKit die Abnahme in einer realen Testumgebung?

Nein. Sie prüfen den clientseitigen Zustandsautomaten und Fehlerpfade. Produktkonfiguration, Serverbenachrichtigungen, Belegverarbeitung und die echte Zahlungsoberfläche benötigen eine separate Abnahme.

Warum schlagen Kauftests nur innerhalb der gesamten Testsuite sporadisch fehl?

Häufig bleiben Transaktionen aus vorherigen Tests erhalten. Jede Testmethode sollte eine neue SKTestSession verwenden, Transaktionen löschen und Tests mit gemeinsamem Produktzustand seriell ausführen.

Welche Belege sollte CI bei einem Fehler archivieren?

Mindestens xcresult, Testprotokoll, Version der StoreKit-Konfiguration, Commit-Hash, Xcode-Version und Simulator-Laufzeit gehören zusammen in das Fehlerartefakt.

OAKVM BUILD NODE

Den nächsten Build auf einem dedizierten physischen Knoten ausführen

Wählen Sie OakVM M4 oder OakVM M4 Pro und mieten Sie einen Cloud-Mac ohne Virtualisierung – tage-, wochen-, monats- oder quartalsweise. Der aktuelle Verfügbarkeitsstatus des Knotens wird in Echtzeit von der Konsole angezeigt.

Konfiguration auswählen und mieten