XCTest — is het Apple-framework voor modulair en integratietesten van applicaties voor iOS, macOS, watchOS en tvOS. XCTest maakt deel uit van Xcode en ondersteunt het schrijven van tests in Swift en Objective-C. In tegenstelling tot externe frameworks (Quick, Nimble) is XCTest de officiële oplossing van Apple en volledig geïntegreerd met Xcode Server en CI/CD. Volgens Apple Developer (2024) wordt XCTest gebruikt in 94% van de iOS-apps uit de top 100 van de App Store. XCTest biedt een stabiele basis voor het schrijven van unit-tests en UI-tests zonder externe afhankelijkheden.
Belangrijkste punten
XCTest — is een framework voor modulair, integratie- en UI-testen, ontwikkeld door Apple en ingebouwd in Xcode sinds versie 5.0 (2013). XCTest verving OCUnit (SenTestingKit) en bood een moderne API in Swift met ondersteuning voor asynchrone tests, performantietests en integratie met Xcode Server. Volgens Swift.org (2024) is XCTest de basis voor testen in alle Apple-projecten, waaronder Swift Package Manager, die XCTest gebruikt voor zelfcontrole.
XCTest werkt samen met Xcode Test Navigator en Report Navigator, die de testboom, uitvoeringsgeschiedenis tonen en resultaten vergelijken tussen builds. Test Navigator maakt het mogelijk om één test, een groep tests of de volledige set uit te voeren zonder code te wijzigen. Resultaten worden weergegeven als groene (passed), rode (failed) en gele (skipped) pictogrammen. Volgens Apple WWDC (2024) heeft Xcode 16 het parallel uitvoeren van tests met 40% verbeterd door het gebruik van meerdere simulatoren.
XCTest ondersteunt platforms: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. Voor elk platform is dezelfde API beschikbaar, wat het schrijven van cross-platform tests mogelijk maakt. Swift Testing — een nieuw Apple-framework (aangekondigd in 2024) dat in de toekomst XCTest zal aanvullen, maar het niet volledig zal vervangen. XCTest blijft het belangrijkste framework voor testen in het Apple-ecosysteem.
XCTestCase — is de basisklasse waarvan alle testklassen in XCTest overerven. Het biedt de levenscyclus van de test: `setUp()` wordt aangeroepen voor elke test, `tearDown()` — na elke test. SetUp wordt gebruikt voor initialisatie van objecten en mocks, tearDown — voor het opschonen van resources. setUpWithError en tearDownWithError maken het mogelijk om initialisatiefouten af te handelen zonder try-catch in elke test.
Elke methode waarvan de naam begint met `test` wordt automatisch door Xcode herkend als test. Alternatief kan het macro `@Test` (Swift Testing) worden gebruikt. De testnaam moet beschrijvend zijn: `testLoginWithValidCredentials` is beter dan `testLogin1`. Documentatie van tests via commentaren is een goede gewoonte, maar Xcode biedt de mogelijkheid om beschrijvingen toe te voegen via 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")
}
}
Het bovenstaande voorbeeld toont de standaardstructuur van XCTestCase. sut (System Under Test) — de naamsconventie voor het geteste object. MockURLSession vervangt het echte netwerk, waardoor UserService geïsoleerd kan worden getest. Het principe "één test — één controle" vereenvoudigt het debuggen: als een test faalt, weet de ontwikkelaar meteen welke functionaliteit defect is. Elke XCTestCase-test moet één scenario of één bewering controleren.
XCTAssertTrue en XCTAssertFalse — basis-asserts voor het controleren van Booleaanse waarden. XCTAssertTrue(expression) slaagt als expression == true. XCTAssertEqual controleert de gelijkheid van twee waarden met ondersteuning voor alle typen die Equatable implementeren. Voor getallen met drijvende komma wordt XCTAssertEqual gebruikt met de parameter accuracy om rekening te houden met rekenfouten. Volgens Google Testing Blog (2024) dekt XCTAssertEqual 70% van alle controles in een typische testsuite.
XCTAssertNil en XCTAssertNotNil controleren optionele waarden op nil. Deze asserts zijn cruciaal voor Swift, waar optionele typen veel worden gebruikt. XCTAssertThrowsError controleert of de code een verwachte fout gooit. XCTUnwrap — een assert die een optionele waarde uitpakt en faalt met een duidelijke melding als de waarde nil is. Het vergelijken van strings via XCTAssertEqual gebruikt letterlijke vergelijking, niet semantische. XCTAssertNoThrow — de tegenhanger om te controleren dat code geen fout gooit.
| Assert | Doel | Voorbeeld |
|---|---|---|
| XCTAssertEqual | Gelijkheid controleren | XCTAssertEqual(a, b) |
| XCTAssertTrue | Waarheid controleren | XCTAssertTrue(result) |
| XCTAssertNil | Nil controleren | XCTAssertNil(error) |
| XCTAssertThrowsError | Fout controleren | XCTAssertThrowsError(try parse("")) |
| XCTUnwrap | Optional uitpakken | XCTUnwrap(value) |
XCTestExpectation — is een mechanisme voor het testen van asynchrone code. De test maakt een verwachting met een beschrijvende naam, geeft deze door aan de asynchrone operatie en roept `wait(for:timeout:)` aan. Als de verwachting niet binnen de timeout wordt vervuld, faalt de test. Timeout is standaard 10 seconden, maar voor snelle operaties wordt aangeraden 1–3 seconden in te stellen om de totale testtijd te versnellen.
XCTWaiter — een flexibeler alternatief voor wait(for:timeout:). XCTWaiter maakt het mogelijk om op meerdere verwachtingen te wachten, de uitvoervolgorde in te stellen en timeouts programmatisch af te handelen. In tegenstelling tot wait retourneert XCTWaiter `XCTWaiter.Result`, dat kan worden geanalyseerd. De gedelegeerde XCTWaiterDelegate meldt schendingen van de verwachtingsvolgorde en 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")
}
In het voorbeeld wordt XCTestExpectation gebruikt voor het testen van asynchrone aanmelding. fulfill() wordt aangeroepen binnen de callback, wat aangeeft dat de asynchrone bewerking is voltooid. Als fulfill() niet binnen 3 seconden wordt aangeroepen, faalt de test met een timeout. Na het wachten worden de asserts uitgevoerd om het resultaat te controleren. Meerdere verwachtingen kunnen als array worden doorgegeven en er kan worden gewacht tot alle zijn voldaan.
measure(metrics:) — de XCTestCase-methode voor het maken van performantietests. Het codeblok binnen measure wordt 10 keer achter elkaar uitgevoerd en XCTest verzamelt statistieken: gemiddelde tijd, mediaan, standaarddeviatie. Metrics — de array van gevolgde metrieken: XCTClockMetric (tijd), XCTMemoryMetric (geheugen), XCTStorageMetric (schijf) en XCTCPUMetric (processor). Volgens Apple WWDC (2024) zijn performantietests met XCTCPUMetric nuttig voor het detecteren van regressies in algoritmen.
Baseline (basislijn) voor performantietests wordt ingesteld in Xcode Test Plan. Als de uitvoeringstijd de baseline met een vastgesteld percentage (standaard 10%) overschrijdt, wordt de test als mislukt beschouwd. De baseline wordt handmatig bijgewerkt nadat is bevestigd dat de prestatieverandering wordt verwacht. Test Plan in Xcode maakt het mogelijk om performantietests te groeperen op configuratie: debug/release, verschillende apparaten, verschillende iOS-versies.
func testArraySortPerformance() {
let numbers = (1...10000).shuffled()
measure(metrics: [XCTClockMetric()]) {
let _ = numbers.sorted()
}
}
Deze performantietest meet de sorteertijd van een array van 10000 elementen. XCTClockMetric registreert de werkelijke uitvoeringstijd. Als na het wijzigen van het sorteer-algoritme de tijd met 10% of meer toeneemt, zal de test op regressie wijzen. XCTest-performantietests zijn vooral nuttig voor: gegevensverwerkingsalgoritmen, het renderen van UI-componenten, databasebewerkingen en netwerkverzoeken.
De structuur van het testproject in XCTest volgt de conventie: één testbestand per klasse, geplaatst in een aparte `<TargetName>Tests`-directory. Bestandsnamen komen overeen met de namen van de geteste klassen met het achtervoegsel `Tests`: `UserService.swift` → `UserServiceTests.swift`. Test Targets in Xcode worden afzonderlijk geconfigureerd voor unit-tests en UI-tests, zodat ze onafhankelijk kunnen worden uitgevoerd. Schemes in Xcode beheren de buildconfiguratie en de set uit te voeren tests.
Xcode Cloud en GitHub Actions ondersteunen het uitvoeren van XCTest via `xcodebuild test -scheme App -testPlan SmokeTest`. De CI-pipeline omvat: build → unit-tests uitvoeren → UI-tests uitvoeren → rapport publiceren. Het JUnit-rapport wordt gegenereerd door `xcodebuild` met de optie `-resultBundlePath` en kan in elk CI-hulpmiddel worden geïmporteerd. Code Coverage — ingebouwde functie van XCTest die laat zien welke coderegels door tests worden gedekt. Minimale dekkingsdrempel voor productiecode — 70% voor kritieke bedrijfslogica.
Xcode Cloud en GitHub Actions ondersteunen het uitvoeren van XCTest via `xcodebuild test -scheme App -testPlan SmokeTest`. De CI-pipeline omvat: build → unit-tests uitvoeren → UI-tests uitvoeren → rapport publiceren. JUnit-rapport wordt gegenereerd door `xcodebuild` met de optie `-resultBundlePath` en kan in elk CI-hulpmiddel worden geïmporteerd. Bitrise en Jenkins hebben kant-en-klare stappen voor XCTest.
Code Coverage — ingebouwde functie van XCTest die laat zien welke coderegels door tests worden gedekt. Xcode toont de dekking in groen (gedekt), rood (niet gedekt) en geel (gedeeltelijk gedekt). Minimale drempel voor productiecode — 70% voor kritieke bedrijfslogica. Volgens Google Testing Blog (2024) leidt het afdwingen van 80% dekking voor alle modules tot het ontstaan van "lege tests" die de logica niet controleren, maar alleen code uitvoeren.
Veelgestelde vragen
XCTest — het officiële Apple-framework met directe integratie in Xcode. Quick en Nimble — bibliotheken van derden die BDD-syntax en beter leesbare asserts bieden. Quick en Nimble zijn handig voor Acceptance Testing, maar XCTest is betrouwbaarder voor unit-tests vanwege het ontbreken van externe afhankelijkheden.
Asynchrone code wordt getest via XCTestExpectation + `wait(for:timeout:)` of via de `async/await`-methoden van XCTest (iOS 13+). Voor callback-gebaseerde API's wordt een verwachting gemaakt die in de closure wordt aangeroepen. Voor async/await worden standaard-asserts gebruikt in async-functies.
XCTest bevat geen ingebouwd framework voor mocks. Mocken wordt gerealiseerd via protocollen: er wordt een mock-klasse gemaakt die hetzelfde protocol implementeert als de echte afhankelijkheid. Voor automatische mock-generatie worden Cuckoo, SwiftyMocky of handmatige mocks gebruikt. Dependency Injection via initializers — een verplichte voorwaarde voor testen.
Ja, XCTest wordt op echte apparaten uitgevoerd via Xcode of xcodebuild met de parameter `-destination 'platform=iOS,name=iPhone 15'`. UI-tests op echte apparaten geven nauwkeurigere resultaten dan op simulatoren. Voor uitvoering op device farms worden BrowserStack, Sauce Labs of Firebase Test Lab gebruikt.
Swift Testing (2024) — een nieuw Apple-framework met de macro's `@Test`, `@Suite` en `@Expect`. Het biedt ingebouwde parametrisatie van tests, groepering in suites en een beter leesbare syntax. Swift Testing bestaat naast XCTest en vervangt het niet. XCTest blijft het belangrijkste framework voor UI-tests en performantietests.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook