Regression der iOS-Codeabdeckung mit xccov auf einem Cloud-Mac prüfen
Nachdem ein Team eine Reihe von Unit-Tests auf einen Cloud-Mac verlagert hat, besteht der häufigste Fehler darin, nur darauf zu achten, ob die Tests erfolgreich sind. Ein Commit kann Assertions für einen kritischen Zweig entfernen, während weiterhin alle Tests grün bleiben und die Codeabdeckung unbemerkt sinkt. Robuster ist es, bei jedem Build ein separates xcresult zu erzeugen und die Daten anschließend mit dem in Xcode enthaltenen xccov zu extrahieren. Dabei sollten sowohl die Gesamtabdeckung des Projekts als auch die in der aktuellen Änderung betroffenen Dateien geprüft werden.
Eingabebedingungen für die Codeabdeckung festlegen
Codeabdeckungswerte sind nur dann sinnvoll vergleichbar, wenn der Testeinstieg stabil bleibt. Legen Sie zunächst die Xcode-Version, das Scheme, den Testplan, das Simulatormodell und die Betriebssystemversion fest. Verschiedene Runner sollten das Destination-Ziel nicht eigenständig auswählen. Der Testbefehl muss die Codeabdeckung außerdem ausdrücklich aktivieren und für jeden Lauf ein neues Ergebnisverzeichnis anlegen.
set -euo pipefail
RESULT_DIR="$PWD/Artifacts/Coverage"
RESULT_BUNDLE="$RESULT_DIR/TestResults.xcresult"
rm -rf "$RESULT_BUNDLE"
mkdir -p "$RESULT_DIR"
xcodebuild test \
-workspace ExampleApp.xcworkspace \
-scheme ExampleApp-CI \
-testPlan UnitTests \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-enableCodeCoverage YES \
-resultBundlePath "$RESULT_BUNDLE"
Der mit resultBundlePath angegebene Pfad darf vor der Ausführung nicht vorhanden sein, da alte Ergebnisse sonst das Schreiben verhindern können. Das CI-Skript sollte außerdem die Ausgabe von xcodebuild -version, die Kennung des aktuellen Commits und das vollständige Destination-Ziel protokollieren. So werden Änderungen an der Toolchain nicht fälschlich als Regression im Code bewertet.
Ein Grenzwert für die Codeabdeckung misst Veränderungen unter identischen Testbedingungen. Er dient nicht dazu, unterschiedliche Simulatoren oder Testpläne miteinander zu vergleichen.
Überprüfbare Daten aus xcresult extrahieren
Exportieren Sie nach Abschluss der Tests mit xccov eine JSON-Datei, statt den formatierten Text aus dem Terminal zu parsen. JSON eignet sich besser, um Dateipfade, Target-Namen und die Zeilenabdeckung zuverlässig zu erfassen. Außerdem verschieben sich die Felder nicht, wenn sich die Anzeigebreite ändert.
xcrun xccov view \
--report \
--json \
"$RESULT_BUNDLE" > "$RESULT_DIR/coverage.json"
test -s "$RESULT_DIR/coverage.json"
Der Parser sollte zunächst prüfen, ob targets vorhanden ist, und die Dateien danach je Target zusammenfassen. Ein leerer Bericht darf nicht als 0% Codeabdeckung in den Vergleich eingehen, sondern muss unmittelbar als Fehler bei der Datenerfassung markiert werden. Häufige Ursachen sind ein Scheme ohne aktivierte Tests, ein Target, das nicht in die Abdeckungsmessung einbezogen wurde, oder ein vorzeitiger Abbruch des Testprozesses, bevor der Bericht geschrieben werden konnte.
Rohdaten als Nachweis aufbewahren
Es empfiehlt sich, die folgenden Dateien als Artefakte desselben Jobs zu speichern:
| Artefakt | Zweck |
|---|---|
TestResults.xcresult |
Tests, Protokolle und Herkunft der Abdeckungsdaten nachvollziehen |
coverage.json |
Stabile Auswertung durch Skripte |
coverage-summary.json |
Grenzwerte, Istwerte und fehlgeschlagene Dateien festhalten |
command.txt |
Ausführungsparameter und Toolchain rekonstruieren |
Ein Screenshot mit Prozentwerten beantwortet nicht die Frage, welche Tests tatsächlich ausgeführt wurden. Für die Fehleranalyse ist das ursprüngliche Ergebnispaket entscheidend.
Zwei Prüfstufen gleichzeitig einsetzen
Die Gesamtabdeckung des Projekts eignet sich gut, um deutliche Rückgänge zu erkennen. Wenn in einer großen Codebasis jedoch einige Dutzend ungetestete Zeilen hinzukommen, verändert sich der Gesamtwert möglicherweise nur um wenige Zehntelprozentpunkte. Deshalb empfiehlt sich eine Prüfung auf zwei Ebenen:
- Die Gesamtabdeckung des Projekts darf einen festen Mindestwert nicht unterschreiten.
- Ausführbare Quelldateien, die von der aktuellen Änderung betroffen sind, müssen einen höheren Grenzwert erreichen.
Beispielsweise kann der Mindestwert für das gesamte Projekt auf 72% und für geänderte Dateien auf 85% festgelegt werden. Die Grenzwerte sollten sich an der aktuellen Ausgangsbasis des Teams orientieren und nicht sofort auf einen idealisierten, praktisch unerreichbaren Wert gesetzt werden. Das folgende Python-Beispiel liest die Abdeckung eines Targets aus. In einem realen Projekt kann files zusätzlich ausgewertet werden, um jede Datei einzeln zu prüfen.
import json
import sys
with open("Artifacts/Coverage/coverage.json", encoding="utf-8") as f:
report = json.load(f)
targets = report.get("targets", [])
if not targets:
raise SystemExit("Coverage report has no targets")
tested = [t for t in targets if t.get("name") == "ExampleApp.app"]
if len(tested) != 1:
raise SystemExit("Expected application target was not found")
coverage = float(tested[0]["lineCoverage"]) * 100
minimum = 72.0
print(f"application_line_coverage={coverage:.2f}")
sys.exit(0 if coverage >= minimum else 2)
Das Skript sollte unterschiedliche Exit-Codes verwenden, um zwischen einer Unterschreitung des Grenzwerts und einem nicht auswertbaren Bericht zu unterscheiden. Im ersten Fall müssen Tests ergänzt oder die Änderung begründet werden. Im zweiten Fall ist die Erfassungskette zu reparieren. Beide Ursachen dürfen nicht als derselbe Fehler behandelt werden.
Nur Dateien prüfen, die tatsächlich Tests benötigen
Die geänderten Dateien lassen sich mit git diff --name-only ermitteln und anschließend mit den Pfaden aus dem xccov-Bericht abgleichen. Es sollten nicht pauschal alle .swift-Dateien in die Prüfung aufgenommen werden. Folgende Inhalte müssen in der Regel ausdrücklich ausgeschlossen werden:
- automatisch generierte Ressourcenzugriffe und API-Clients;
- Hilfscode für Tests im Verzeichnis
Tests; - Modelldateien, die nur Deklarationen und keine ausführbare Logik enthalten;
- von Build-Werkzeugen generierter Quellcode, der nicht manuell gepflegt wird.
Die Ausschlussregeln sollten im Repository liegen und einem Code-Review unterliegen. Nach einem fehlgeschlagenen Lauf darf nicht kurzfristig ein zusätzliches Platzhaltermuster eingeführt werden. Vor dem Vergleich der Pfade müssen außerdem das Repository-Stammverzeichnis vereinheitlicht, symbolische Links aufgelöst und Präfixe temporärer Build-Verzeichnisse entfernt werden. Andernfalls kann dieselbe Datei aufgrund unterschiedlicher absoluter Pfade nicht zugeordnet werden.
Bei umbenannten Dateien ist git diff --name-status -M zuverlässiger als eine einfache Dateiliste. Wurde eine Datei vom alten an einen neuen Pfad verschoben, muss der Berichtseintrag unter dem neuen Pfad gesucht werden. Die Datei darf nicht fälschlich als „im Bericht nicht vorhanden“ gelten.
Parallele Tests und Schwankungen kontrollieren
Gelegentliche Schwankungen der Codeabdeckung entstehen normalerweise nicht durch Zufall innerhalb von xccov, sondern durch eine nicht deterministische Testausführung. Asynchrone Tests müssen auf einen eindeutig definierten Zustand warten und dürfen sich nicht auf eine feste Wartezeit verlassen. Gemeinsam genutzte Datenbanken, Singletons und temporäre Verzeichnisse sind vor jedem Test zurückzusetzen. Wenn parallele Tests um dieselbe Ressource konkurrieren, kann die parallele Ausführung zunächst für die betroffenen Test-Targets deaktiviert werden, um anschließend die konkrete gemeinsam genutzte Stelle zu identifizieren.
Auch der Lebenszyklus des Simulators muss nachvollziehbar sein. Bei langlebigen Runnern können die App-Daten vor jedem Job bereinigt werden; es ist jedoch nicht erforderlich, jedes Mal sämtliche Runtimes zu löschen. Entscheidend ist, ob derselbe Testplan mit derselben Toolchain wiederholt identische Ergebnisse liefert. Dazu kann derselbe Commit zunächst dreimal ausgeführt und die Abweichung für jede Datei protokolliert werden. Dateien mit anhaltenden Schwankungen sollten erst nach einer besseren Testisolation in eine strenge Prüfung aufgenommen werden.
Die Fehlerzusammenfassung sollte schließlich alle Informationen enthalten, mit denen Entwickler direkt handeln können: die tatsächliche Gesamtabdeckung, den geforderten Wert, geänderte Dateien unterhalb des Grenzwerts, die Anzahl ihrer abgedeckten und ausführbaren Zeilen sowie den Archivspeicherort des ursprünglichen xcresult. So lässt sich ohne erneute Ausführung der gesamten Pipeline entscheiden, ob Tests ergänzt, das Skript korrigiert oder Unsicherheiten in der Testumgebung behoben werden müssen.
Häufig gestellte Fragen
Reicht ein Grenzwert für die gesamte Projektabdeckung aus?
Nein. Der Gesamtwert erkennt deutliche Rückgänge, reagiert aber kaum auf ein kleines neues Modul. Prüfen Sie zusätzlich geänderte Dateien und schließen Sie generierte Quellen sowie Testhilfen ausdrücklich aus.
Warum schwankt die Abdeckung beim gleichen Commit?
Typische Ursachen sind eine abweichende Testauswahl, nicht abgeschlossene asynchrone Arbeit, geteilter Zustand paralleler Tests, alte Simulatordaten und verschiedene Xcode-Versionen. Fixieren Sie zuerst alle Testparameter.
Welche Artefakte sollten nach einem Fehler erhalten bleiben?
Sichern Sie das ursprüngliche xcresult, coverage.json, die Grenzwertentscheidung, den exakten Testbefehl und die Commit-Kennung. Damit lassen sich fehlende Tests, Analysefehler und echte Rückgänge unterscheiden.
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.