XCTest ist ein Apple-Framework für Unit- und Integrationstests von Anwendungen für iOS, macOS, watchOS und tvOS. XCTest ist Teil von Xcode und unterstützt das Schreiben von Tests in Swift und Objective-C. Im Gegensatz zu Drittanbieter-Frameworks (Quick, Nimble) ist XCTest die offizielle Lösung von Apple und vollständig in Xcode Server und CI/CD integriert. Laut Apple Developer (2024) wird XCTest in 94 % der iOS-Apps aus den Top 100 des App Store verwendet. XCTest bietet eine stabile Grundlage zum Schreiben von Unit-Tests und UI-Tests ohne externe Abhängigkeiten.
Wichtige Punkte
XCTest ist ein Framework für Unit-, Integrations- und UI-Tests, das von Apple entwickelt und seit Version 5.0 (2013) in Xcode integriert ist. XCTest löste OCUnit (SenTestingKit) ab und bot eine moderne Swift-API mit Unterstützung für asynchrone Tests, Performance-Tests und Xcode Server-Integration. Laut Swift.org (2024) ist XCTest die Grundlage für Tests in allen Apple-Projekten, einschließlich Swift Package Manager, der XCTest zur Selbstvalidierung verwendet.
XCTest arbeitet mit Xcode Test Navigator und Report Navigator zusammen, die den Testbaum, den Verlauf der Durchläufe anzeigen und Ergebnisse zwischen Builds vergleichen. Test Navigator ermöglicht das Ausführen eines einzelnen Tests, einer Testgruppe oder der gesamten Suite ohne Codeänderung. Ergebnisse werden mit grünen (bestanden), roten (fehlgeschlagen) und gelben (übersprungen) Symbolen angezeigt. Laut Apple WWDC (2024) verbesserte Xcode 16 die parallele Testausführung um 40 % durch die Verwendung mehrerer Simulatoren.
XCTest unterstützt Plattformen: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. Jede Plattform hat dieselbe API, was plattformübergreifendes Testen ermöglicht. Swift Testing — ein neues Framework von Apple (angekündigt 2024) — wird XCTest in Zukunft ergänzen, aber nicht vollständig ersetzen. XCTest bleibt das primäre Testframework im Apple-Ökosystem.
XCTestCase ist die Basisklasse, von der alle Testklassen in XCTest erben. Sie stellt den Testlebenszyklus bereit: `setUp()` wird vor jedem Test aufgerufen, `tearDown()` nach jedem Test. setUp wird zum Initialisieren von Objekten und Mocks verwendet, tearDown zum Bereinigen von Ressourcen. setUpWithError und tearDownWithError ermöglichen die Behandlung von Initialisierungsfehlern ohne try-catch in jedem Test.
Jede Methode, deren Name mit `test` beginnt, wird automatisch von Xcode als Test erkannt. Alternativ kann das Makro `@Test` (Swift Testing) verwendet werden. Testnamen sollten beschreibend sein: `testLoginWithValidCredentials` ist besser als `testLogin1`. Dokumentation von Tests durch Kommentare ist eine gute Praxis, aber Xcode erlaubt auch das Hinzufügen von Beschreibungen über User-Defined Attributes.
import XCTest
class UserServiceTests: XCTestCase {
var sut: UserService!
var mockSession: MockURLSession!
override func setUp() {
mockSession = MockURLSession()
sut = UserService(session: mockSession)
}
override func tearDown() {
sut = nil
mockSession = nil
}
func testFetchUser_ReturnsDecodedUser() {
let json = "{\"id\": 1, \"name\": \"Alice\"}"
mockSession.setResponse(json)
let user = try await sut.fetchUser(id: 1)
XCTAssertEqual(user.name, "Alice")
}
}
Das obige Beispiel zeigt die Standardstruktur von XCTestCase. sut (System Under Test) ist eine Namenskonvention für das zu testende Objekt. MockURLSession ersetzt das reale Netzwerk und ermöglicht das isolierte Testen von UserService. Das Prinzip „ein Test — eine Assertion“ vereinfacht das Debugging: Wenn ein Test fehlschlägt, weiß der Entwickler sofort, welche Funktionalität defekt ist. Jeder XCTestCase-Test sollte ein Szenario oder eine Assertion überprüfen.
XCTAssertTrue und XCTAssertFalse sind grundlegende Assertions zur Überprüfung boolescher Werte. XCTAssertTrue(expression) besteht, wenn expression == true ist. XCTAssertEqual überprüft die Gleichheit zweier Werte mit Unterstützung aller Typen, die Equatable implementieren. Für Gleitkommazahlen wird XCTAssertEqual mit dem Parameter accuracy verwendet, um Rechenungenauigkeiten zu berücksichtigen. Laut Google Testing Blog (2024) deckt XCTAssertEqual 70 % aller Prüfungen in einer typischen Testsuite ab.
XCTAssertNil und XCTAssertNotNil überprüfen optionale Werte auf nil. Diese Assertions sind in Swift von entscheidender Bedeutung, wo optionale Typen weit verbreitet sind. XCTAssertThrowsError überprüft, ob Code einen erwarteten Fehler wirft. XCTUnwrap ist eine Assertion, die einen optionalen Wert entpackt und mit einer klaren Nachricht fehlschlägt, wenn der Wert nil ist. Der String-Vergleich über XCTAssertEqual verwendet einen literalen Vergleich, nicht semantisch. XCTAssertNoThrow ist die zugehörige Assertion zur Überprüfung, dass Code keinen Fehler wirft.
| Assertion | Zweck | Beispiel |
|---|---|---|
| XCTAssertEqual | Gleichheitsprüfung | XCTAssertEqual(a, b) |
| XCTAssertTrue | Wahrheitsprüfung | XCTAssertTrue(result) |
| XCTAssertNil | Nil-Prüfung | XCTAssertNil(error) |
| XCTAssertThrowsError | Fehlerprüfung | XCTAssertThrowsError(try parse("")) |
| XCTUnwrap | Optional entpacken | XCTUnwrap(value) |
XCTestExpectation ist ein Mechanismus zum Testen von asynchronem Code. Der Test erstellt eine Erwartung mit einem beschreibenden Namen, übergibt sie an eine asynchrone Operation und ruft `wait(for:timeout:)` auf. Wenn die Erwartung nicht innerhalb des Timeouts erfüllt wird, schlägt der Test fehl. Timeout beträgt standardmäßig 10 Sekunden, aber für schnelle Operationen wird empfohlen, 1–3 Sekunden einzustellen, um die Gesamttestzeit zu verkürzen.
XCTWaiter ist eine flexiblere Alternative zu wait(for:timeout:). XCTWaiter ermöglicht das Warten auf mehrere Erwartungen, das Konfigurieren der Ausführungsreihenfolge und die programmatische Behandlung von Timeouts. Im Gegensatz zu wait gibt XCTWaiter `XCTWaiter.Result` zurück, das analysiert werden kann. Delegate XCTWaiterDelegate benachrichtigt über Verstöße gegen die Erwartungsreihenfolge und Timeouts.
func testAsyncLogin() {
let expectation = XCTestExpectation(description: "login")
var resultUser: User?
sut.login(email: "a@b.com", password: "123") { user in
resultUser = user
expectation.fulfill()
}
wait(for: [expectation], timeout: 3)
XCTAssertNotNil(resultUser)
XCTAssertEqual(resultUser?.email, "a@b.com")
}
Im Beispiel wird XCTestExpectation zum Testen eines asynchronen Logins verwendet. fulfill() wird innerhalb des Callback-Closures aufgerufen und signalisiert, dass die asynchrone Operation abgeschlossen ist. Wenn fulfill() nicht innerhalb von 3 Sekunden aufgerufen wird, schlägt der Test mit einem Timeout fehl. Nach erfolgreichem Warten werden Assertions zur Überprüfung des Ergebnisses durchgeführt. Mehrere Erwartungen können als Array übergeben werden und auf die Erfüllung aller gewartet werden.
measure(metrics:) ist eine XCTestCase-Methode zum Erstellen von Performance-Tests. Der Codeblock innerhalb von measure wird 10 Mal hintereinander ausgeführt, und XCTest sammelt Statistiken: Durchschnittszeit, Median, Standardabweichung. Metrics ist ein Array von verfolgten Metriken: XCTClockMetric (Zeit), XCTMemoryMetric (Speicher), XCTStorageMetric (Festplatte) und XCTCPUMetric (Prozessor). Laut Apple WWDC (2024) sind Performance-Tests mit XCTCPUMetric nützlich, um Regressionen in Algorithmen zu erkennen.
Baseline (Basislinie) für Performance-Tests wird im Xcode Test Plan festgelegt. Wenn die Ausführungszeit die Basislinie um einen festgelegten Prozentsatz (Standard 10 %) überschreitet, gilt der Test als fehlgeschlagen. Die Basislinie wird manuell aktualisiert, nachdem bestätigt wurde, dass die Leistungsänderung erwartet wird. Test Plan in Xcode ermöglicht das Gruppieren von Performance-Tests nach Konfigurationen: Debug/Release, verschiedene Geräte, verschiedene iOS-Versionen.
func testArraySortPerformance() {
let numbers = (1...10000).shuffled()
measure(metrics: [XCTClockMetric()]) {
let _ = numbers.sorted()
}
}
Dieser Performance-Test misst die Sortierzeit eines Arrays mit 10.000 Elementen. XCTClockMetric erfasst die tatsächliche Ausführungszeit. Wenn sich die Zeit nach einer Änderung des Sortieralgorithmus um 10 % oder mehr erhöht, zeigt der Test eine Regression an. XCTest-Performance-Tests sind besonders nützlich für: Datenverarbeitungsalgorithmen, Rendering von UI-Komponenten, Datenbankoperationen und Netzwerkanfragen.
Die Testprojektstruktur in XCTest folgt der Konvention: eine Testdatei pro Klasse, abgelegt in einem separaten Verzeichnis `<TargetName>Tests`. Dateinamen entsprechen den Namen der getesteten Klassen mit dem Suffix `Tests`: `UserService.swift` → `UserServiceTests.swift`. Test Targets in Xcode werden getrennt für Unit-Tests und UI-Tests konfiguriert, sodass sie unabhängig ausgeführt werden können. Schemes in Xcode verwalten die Build-Konfiguration und den Satz der auszuführenden Tests.
Xcode Cloud und GitHub Actions unterstützen die Ausführung von XCTest über `xcodebuild test -scheme App -testPlan SmokeTest`. Die CI-Pipeline umfasst: Build → Ausführen von Unit-Tests → Ausführen von UI-Tests → Veröffentlichen des Berichts. Der JUnit-Bericht wird über `xcodebuild` mit der Option `-resultBundlePath` generiert und kann in jedes CI-Tool importiert werden. Code Coverage ist eine integrierte XCTest-Funktion, die anzeigt, welche Codezeilen von Tests abgedeckt werden. Die Mindestschwelle für die Abdeckung von Produktionscode beträgt 70 % für kritische Geschäftslogik.
Xcode Cloud und GitHub Actions unterstützen die Ausführung von XCTest über `xcodebuild test -scheme App -testPlan SmokeTest`. Die CI-Pipeline umfasst: Build → Ausführen von Unit-Tests → Ausführen von UI-Tests → Veröffentlichen des Berichts. JUnit-Bericht wird über `xcodebuild` mit der Option `-resultBundlePath` generiert und kann in jedes CI-Tool importiert werden. Bitrise und Jenkins haben vorgefertigte Schritte für XCTest.
Code Coverage ist eine integrierte XCTest-Funktion, die anzeigt, welche Codezeilen von Tests abgedeckt werden. Xcode zeigt die Abdeckung grün (abgedeckt), rot (nicht abgedeckt) und gelb (teilweise abgedeckt) an. Mindestschwelle für die Abdeckung von Produktionscode beträgt 70 % für kritische Geschäftslogik. Laut Google Testing Blog (2024) führt die Durchsetzung von 80 % Abdeckung für alle Module zu „leeren Tests“, die keine Logik überprüfen, sondern nur Code ausführen.
Häufig gestellte Fragen
XCTest ist Apples offizielles Framework mit direkter Xcode-Integration. Quick und Nimble sind Drittanbieter-Bibliotheken, die BDD-Syntax und lesbarere Assertions bieten. Quick und Nimble sind praktisch für Acceptance Testing, aber XCTest ist aufgrund fehlender externer Abhängigkeiten zuverlässiger für Unit-Tests.
Asynchroner Code wird über XCTestExpectation + `wait(for:timeout:)` oder über `async/await`-Methoden von XCTest (iOS 13+) getestet. Für Callback-basierte APIs wird eine Erwartung erstellt, die im Closure erfüllt wird. Für async/await werden Standard-Assertions in async-Funktionen verwendet.
XCTest enthält kein integriertes Mocking-Framework. Mocking wird über Protokolle implementiert: Eine Mock-Klasse wird erstellt, die dasselbe Protokoll wie die reale Abhängigkeit implementiert. Für die automatische Generierung von Mocks werden Cuckoo, SwiftyMocky oder manuelle Mocks verwendet. Dependency Injection über Initialisierer ist eine zwingende Voraussetzung für Testbarkeit.
Ja, XCTest wird auf echten Geräten über Xcode oder xcodebuild mit dem Parameter `-destination 'platform=iOS,name=iPhone 15'` ausgeführt. UI-Tests auf echten Geräten liefern genauere Ergebnisse als auf Simulatoren. Für die Ausführung auf Gerätefarmen werden BrowserStack, Sauce Labs oder Firebase Test Lab verwendet.
Swift Testing (2024) ist ein neues Apple-Framework mit den Makros `@Test`, `@Suite` und `@Expect`. Es bietet integrierte Testparametrisierung, Suite-Gruppierung und eine lesbarere Syntax. Swift Testing koexistiert mit XCTest und ersetzt es nicht. XCTest bleibt das primäre Framework für UI-Tests und Performance-Tests.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch