Nach dem Zusammenführen einer Überarbeitung der Startseite bestanden alle Funktionstests, dennoch meldeten Nutzer, dass das erstmalige Öffnen der App länger dauerte. Die Startleistung wird im Entwicklungsalltag besonders leicht übersehen: Eine neue Datenbankmigration, das synchrone Einlesen der Konfiguration, die Initialisierung eines Analysemoduls oder das Laden großer Bilder können unbemerkt zusätzliche Arbeit in den Hauptthread verlagern. Statt darauf zu warten, dass solche Probleme bei manuellen Tests auffallen, sollte die Kaltstartmessung auf einem fest definierten Cloud-Mac als Prüftor für das Zusammenführen eingerichtet werden.
Zuerst ein vergleichbares Startszenario definieren
„Die App öffnen“ ist keine vollständige Testdefinition. Mindestens die Build-Konfiguration, das Simulator-Modell, die Systemlaufzeit, der anfängliche Datenbestand der App und die Startargumente müssen festgelegt werden. Auch das Testziel sollte klar abgegrenzt sein, beispielsweise ausschließlich die Zeit von der Prozesserstellung bis zum Erscheinen der ersten interaktiven Oberfläche. Anmeldeanfragen oder das Herunterladen von Testdaten dürfen das Ergebnis nicht beeinflussen.
Es empfiehlt sich, einen separaten Testplan namens Performance.xctestplan anzulegen, darin ausschließlich die Tests zur Startleistung zu aktivieren und die zufällige Ausführungsreihenfolge zu deaktivieren. Über Startargumente wird die Testumgebung direkt in einen stabilen Zustand versetzt:
final class LaunchPerformanceTests: XCTestCase {
override func setUp() {
continueAfterFailure = false
}
func testColdLaunch() {
let app = XCUIApplication()
app.launchArguments = ["-uiTesting", "-resetLocalState"]
let options = XCTMeasureOptions()
options.iterationCount = 8
measure(
metrics: [XCTApplicationLaunchMetric(waitUntilResponsive: true)],
options: options
) {
app.launch()
}
}
}
-resetLocalState sollte vom Test-Einstiegspunkt der App verarbeitet werden und nur lokal gespeicherte Zustände entfernen, die sich wiederherstellen lassen. Die reguläre Codeausführung darf aufgrund dieses Parameters keine Initialisierung überspringen, die tatsächlich gemessen werden soll. Andernfalls ermittelt der Test lediglich eine künstlich optimierte „Startleistung für Tests“.
Cloud-Mac und Simulator-Zustand fest vorgeben
Ein Prüftor für die Leistung benötigt einen exklusiv genutzten physischen Rechner und darf nicht in einer gemeinsam verwendeten Umgebung ausgeführt werden, in der unbekannte Aufgaben um CPU- und Festplattenressourcen konkurrieren. Vor jedem Durchlauf sollten der aktuelle Xcode-Pfad, die verfügbaren Simulatoren und der freie Speicherplatz geprüft werden:
set -euo pipefail
xcode-select -p
xcodebuild -version
xcrun simctl list devices available
df -h "$HOME"
Im Testplan muss ein bestimmter installierter Simulator ausgewählt werden. Das Skript sollte nicht automatisch das „erste verfügbare Gerät“ verwenden, da Änderungen an den Images die Vergleichbarkeit historischer Daten zunichtemachen. Während das Prüftor ausgeführt wird, sollten außerdem keine parallelen Archivierungen, Abhängigkeitsaktualisierungen oder umfangreichen Indizierungsvorgänge stattfinden.
Leistungsdaten sind in erster Linie Umgebungsdaten und erst danach Codedaten. Messwerte, bei denen sich Ausführungsknoten, Build-Konfiguration und Simulator-Zustand nicht nachvollziehen lassen, eignen sich nicht dazu, das Zusammenführen unmittelbar zu blockieren.
Beim erstmaligen Erstellen einer Basislinie sollte derselbe Commit mehrfach getestet werden. Streuen die Ergebnisse deutlich, sind zunächst Hintergrundaufgaben, die Festplattenauslastung und die Logik zum Zurücksetzen der Testdaten zu untersuchen, statt sofort den Schwellenwert zu lockern.
Ein einzelnes Prüftor mit xcodebuild ausführen
Der Befehlszeilenauftrag führt ausschließlich den vorgesehenen Testfall aus und speichert das Ergebnispaket als Pipeline-Artefakt. Der Gerätename muss durch das im Testplan tatsächlich festgelegte Modell ersetzt werden:
set -o pipefail
mkdir -p Artifacts
xcodebuild test \
-workspace App.xcworkspace \
-scheme App \
-testPlan Performance \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-only-testing:AppUITests/LaunchPerformanceTests/testColdLaunch \
-resultBundlePath Artifacts/Launch.xcresult \
| tee Artifacts/launch-test.log
set -o pipefail ist entscheidend. Andernfalls kann tee am Ende der Pipe selbst nach einem Fehler von xcodebuild dafür sorgen, dass der Auftrag als erfolgreich zurückgemeldet wird. Sowohl Launch.xcresult als auch das Textprotokoll sollten aufbewahrt werden: Das Ergebnispaket enthält die einzelnen Messungen und Testanhänge, während sich das Textprotokoll zur schnellen Diagnose von Fehlern während des Build- oder Startvorgangs eignet.
| Prüfpunkt | Festlegung | Vorgehen nach einer Änderung |
|---|---|---|
| Xcode | Ausgabe von xcodebuild -version protokollieren |
Zuerst eine neue mögliche Basislinie erstellen |
| Simulator | Modell und Laufzeit fest vorgeben | Nicht direkt mit der alten Basislinie vergleichen |
| Build-Konfiguration | Denselben Testplan verwenden | Überschreibungen durch temporäre Parameter ablehnen |
| Testdaten | Über Startargumente deterministisch zurücksetzen | Vollständigkeit des Zurücksetzens prüfen |
| Anzahl der Iterationen | Im Prüftor einheitlich 8 Durchläufe verwenden | Keine Entscheidung anhand eines Einzelergebnisses |
Eine Basislinie pflegen statt Einzelwerten nachzujagen
Im Xcode-Testbericht sollte für den betreffenden Test eine Leistungsbasislinie erstellt und die generierte gemeinsame Basisliniendatei in die Versionsverwaltung aufgenommen werden. Änderungen an der Basislinie müssen wie Änderungen an einer Abhängigkeits-Sperrdatei geprüft werden: Die Commit-Beschreibung muss die Ursache der Änderung, den zur Überprüfung verwendeten Commit und die erwarteten Auswirkungen nennen. Ein bloßer Hinweis wie „Leistungsdaten aktualisiert“ reicht nicht aus.
Bei den Schwellenwerten sollte zwischen Warnungen und Blockierungen unterschieden werden. Geringfügige Schwankungen können zur Trendbeobachtung dienen, während eine Abweichung als Fehler gewertet wird, wenn sie auch nach wiederholten Durchläufen außerhalb des Toleranzbereichs liegt. Entscheidend ist nicht der schnellste Einzelwert, sondern ob mehrere Iterationen in derselben Umgebung stabil von der geprüften Basislinie abweichen.
In den folgenden Fällen sollte die Basislinie nicht unmittelbar aktualisiert werden:
- Auf dem Testknoten wurden gleichzeitig andere ressourcenintensive Aufgaben ausgeführt;
- das Simulator-Modell oder die Laufzeit hat sich geändert;
- Abhängigkeiten wurden erstmals heruntergeladen oder die Indizierung ist noch nicht abgeschlossen;
- Initialisierungsschritte wurden übersprungen, damit der Test besteht;
- nur ein einzelner Durchlauf war auffällig, während alle übrigen Ergebnisse normal waren.
Nach einem Fehler den Zustand sichern und den verantwortlichen Pfad ermitteln
Schlägt das Prüftor fehl, sollte der Test zunächst auf demselben Cloud-Mac und mit demselben Commit erneut ausgeführt werden. Ist das Ergebnis reproduzierbar, wird der Startvorgang in einzelne Phasen zerlegt: Prozesseinstieg, Dependency Injection, Öffnen der Datenbank, Aufbau des Modells für die erste Ansicht, Decodierung der Ressourcen und Rendering des ersten Frames. An diesen Grenzen können einheitliche os_signpost-Markierungen eingefügt werden. So lässt sich bei der anschließenden Analyse erkennen, in welchem Abschnitt die Zeit anfällt, statt die Ursache anhand des Umfangs eines Commits zu erraten.
Die Untersuchung sollte mit den jüngsten Änderungen beginnen, darf sich aber nicht auf den Anwendungscode beschränken. Auch Änderungen an der Build-Konfiguration, Diagnoseoptionen für das Debugging, wachsende Ressourcendateien und Test-Fixtures können den Startpfad beeinflussen. Nach der Korrektur sollten sowohl das fehlgeschlagene als auch das erfolgreiche xcresult aufbewahrt und die zugehörigen Vergleichs-Commits im Merge Request dokumentiert werden.
Der Nutzen eines zuverlässigen Prüftors besteht nicht darin, die Startzeit mit einem absoluten Etikett zu versehen, sondern dasselbe Szenario dauerhaft vergleichbar zu machen. Erst wenn die Umgebung festgelegt, die Basislinie versioniert, die Nachweise gespeichert und Änderungen an der Basislinie einem Review unterzogen werden, entwickelt sich die Startleistung von einer subjektiven Wahrnehmung zu einer nachvollziehbaren technischen Vorgabe.
Häufig gestellte Fragen
Wie viele Durchläufe sollte die Startmessung verwenden?
Führen Sie für denselben Commit mindestens 5 Messungen und im Prüftor besser 8 oder mehr Iterationen aus. Simulator, Runtime, Build-Konfiguration und Hintergrundlast müssen dabei gleich bleiben.
Sollte der Grenzwert nach einem Fehlschlag sofort erhöht werden?
Nein. Wiederholen Sie die Messung zuerst auf demselben Knoten und prüfen Sie Simulator-Zustand sowie Ausführungspfad. Eine neue Basislinie ist nur nach einer bestätigten, beabsichtigten Produktänderung sinnvoll.
Workflows mit einem cloudbasierten Mac reproduzieren, der projektbezogen gestartet und beendet wird
Wählen Sie aus drei Apple-Silicon-Konfigurationen die passende Ressource und mieten Sie sie tage-, wochen-, monats- oder quartalsweise. Die Geräte sind exklusive physische Maschinen, keine virtuellen Maschinen. Maßgeblich ist der in Echtzeit von der Konsole gemeldete Verfügbarkeitsstatus.