XCTest: Schlüsselkonzepte, XCTestCase-Klassen und Schreiben von Tests

Autor: IT Sectr Veröffentlicht: 2026-04-08 Lesezeit: 11 Min.

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 — Apples offizielles Framework für Unit-Testing und UI-Testing in Swift und Objective-C.
  • XCTestCase — die Basisklasse für alle Tests, die setUp, tearDown und Assertionsmethoden bereitstellt.
  • Assertions — XCTAssertTrue, XCTAssertEqual, XCTAssertNil und andere zur Überprüfung erwarteter Ergebnisse.
  • XCTestExpectation — ein Mechanismus zum Testen von asynchronem Code mit Warten auf Erfüllung.
  • Performance-Tests — Messen der Codeausführungszeit mit der Methode measure(metrics:) mit Schwellenwerten.

Was ist XCTest?

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 — die Basisklasse für Tests

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.

swift
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.

Assertions in XCTest

Basisgruppe der Assertions

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.

Nil-Assertions und Fehler

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.

AssertionZweckBeispiel
XCTAssertEqualGleichheitsprüfungXCTAssertEqual(a, b)
XCTAssertTrueWahrheitsprüfungXCTAssertTrue(result)
XCTAssertNilNil-PrüfungXCTAssertNil(error)
XCTAssertThrowsErrorFehlerprüfungXCTAssertThrowsError(try parse(""))
XCTUnwrapOptional entpackenXCTUnwrap(value)

XCTestExpectation und asynchrone Tests

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.

swift
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.

Performance-Tests mit XCTest

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.

swift
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.

Testorganisation und CI/CD-Integration

Testprojektstruktur

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.

CI/CD-Integration

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

Wie unterscheidet sich XCTest von Quick und Nimble?

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.

Wie testet man asynchronen Code in XCTest?

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.

Wie mockt man Abhängigkeiten in XCTest?

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.

Kann XCTest auf echten Geräten ausgeführt werden?

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.

Was ist neu bei Swift Testing im Vergleich zu XCTest?

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

  • XCTest — Apples offizielles Testframework, in Xcode integriert und unterstützt Swift und Objective-C.
  • XCTestCase bietet den setUp/tearDown-Lebenszyklus und eine Reihe von Assertions für Unit-Tests.
  • XCTestExpectation und XCTWaiter sind Mechanismen zum Testen von asynchronem Code mit Callbacks und async/await.
  • Performance-Tests verwenden measure(metrics:) mit Unterstützung für XCTClockMetric, XCTMemoryMetric und XCTCPUMetric.
  • Asserts — XCTAssertEqual, XCTAssertTrue, XCTAssertNil, XCTAssertThrowsError, XCTUnwrap und andere.
  • CI/CD-Integration über xcodebuild, Xcode Cloud und GitHub Actions mit automatischer Berichterstellung.
  • Swift Testing — ein neues Apple-Framework, das XCTest für Unit-Tests und Parametrisierung ergänzt.

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.

Projekt besprechen

Lesen Sie auch