XCTest — to framework Apple do testowania modułowego i integracyjnego aplikacji na iOS, macOS, watchOS i tvOS. XCTest wchodzi w skład Xcode i obsługuje pisanie testów w Swift i Objective-C. W przeciwieństwie do zewnętrznych frameworków (Quick, Nimble), XCTest jest oficjalnym rozwiazaniem Apple i jest w pełni zintegrowany z Xcode Server i CI/CD. Według Apple Developer (2024), XCTest jest używany w 94% aplikacji iOS z top-100 App Store. XCTest zapewnia stabilną podstawę do pisania testów jednostkowych i UI-testów bez zewnętrznych zależności.
Najważniejsze
XCTest — to framework do testowania modułowego, integracyjnego i UI-testowania, opracowany przez Apple i wbudowany w Xcode od wersji 5.0 (2013 rok). XCTest zastąpił OCUnit (SenTestingKit) i udostępnił nowoczesne API w Swift z obsługą testów asynchronicznych, testów wydajnościowych i integracji z Xcode Server. Według Swift.org (2024), XCTest jest podstawą testowania we wszystkich projektach Apple, w tym Swift Package Manager, który używa XCTest do autotestów.
XCTest działa w połączeniu z Xcode Test Navigator i Report Navigator, które pokazują drzewo testów, historię przejść i porównują wyniki między kompilacjami. Test Navigator umożliwia uruchomienie jednego testu, grupy testów lub całego zestawu bez zmiany kodu. Wyniki wyświetlane są jako zielone (passed), czerwone (failed) i żółte (skipped) ikony. Według Apple WWDC (2024), Xcode 16 poprawił równoległe uruchamianie testów o 40% dzięki użyciu wielu symulatorów.
XCTest obsługuje platformy: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. Dla każdej platformy dostępne jest to samo API, co umożliwia pisanie testów wieloplatformowych. Swift Testing — nowy framework od Apple (ogłoszony w 2024), który w przyszłości uzupełni XCTest, ale nie zastąpi go całkowicie. XCTest pozostaje głównym frameworkiem do testowania w ekosystemie Apple.
XCTestCase — to klasa bazowa, po której dziedziczą wszystkie klasy testowe w XCTest. Udostępnia cykl życia testu: `setUp()` wywoływane przed każdym testem, `tearDown()` — po każdym teście. SetUp służy do inicjalizacji obiektów i mocków, tearDown — do czyszczenia zasobów. setUpWithError i tearDownWithError umożliwiają obsługę błędów inicjalizacji bez try-catch w każdym teście.
Każda metoda, której nazwa zaczyna się od `test`, jest automatycznie rozpoznawana przez Xcode jako test. Alternatywnie można użyć makra `@Test` (Swift Testing). Nazwa testu powinna być opisowa: `testLoginWithValidCredentials` jest lepsze niż `testLogin1`. Dokumentowanie testów przez komentarze to dobra praktyka, ale Xcode umożliwia dodawanie opisu przez 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")
}
}
Powyższy przykład pokazuje standardową strukturę XCTestCase. sut (System Under Test) — konwencja nazewnictwa testowanego obiektu. MockURLSession zastępuje rzeczywistą sieć, umożliwiając testowanie UserService w izolacji. Zasada „jeden test — jedna kontrola” upraszcza debugowanie: jeśli test pada, programista od razu wie, która funkcjonalność jest zepsuta. Każdy test XCTestCase powinien sprawdzać jeden scenariusz lub jedno twierdzenie.
XCTAssertTrue i XCTAssertFalse — podstawowe asercje do sprawdzania wartości logicznych. XCTAssertTrue(expression) przechodzi, jeśli expression == true. XCTAssertEqual sprawdza równość dwóch wartości z obsługą wszystkich typów implementujących Equatable. Dla liczb zmiennoprzecinkowych używa się XCTAssertEqual z parametrem accuracy uwzględniającym błąd obliczeń. Według Google Testing Blog (2024), XCTAssertEqual pokrywa 70% wszystkich kontroli w typowym zestawie testowym.
XCTAssertNil i XCTAssertNotNil sprawdzają wartości opcjonalne na nil. Te asercje są krytyczne dla Swift, gdzie typy opcjonalne są szeroko używane. XCTAssertThrowsError sprawdza, czy kod wyrzuca oczekiwany błąd. XCTUnwrap — asercja, która wyodrębnia wartość opcjonalną i kończy się zrozumiałym komunikatem, jeśli wartość jest równa nil. Porównywanie ciągów przez XCTAssertEqual używa porównania dosłownego, a nie semantycznego. XCTAssertNoThrow — asercja parzysta do sprawdzania, że kod nie wyrzuca błędu.
| Asercja | Przeznaczenie | Przykład |
|---|---|---|
| XCTAssertEqual | Sprawdzanie równości | XCTAssertEqual(a, b) |
| XCTAssertTrue | Sprawdzanie prawdy | XCTAssertTrue(result) |
| XCTAssertNil | Sprawdzanie na nil | XCTAssertNil(error) |
| XCTAssertThrowsError | Sprawdzanie błędu | XCTAssertThrowsError(try parse("")) |
| XCTUnwrap | Wyodrębnienie optional | XCTUnwrap(value) |
XCTestExpectation — to mechanizm do testowania kodu asynchronicznego. Test tworzy oczekiwanie z opisową nazwą, przekazuje je do operacji asynchronicznej i wywołuje `wait(for:timeout:)`. Jeśli oczekiwanie nie zostanie spełnione w ciągu czasu oczekiwania, test pada. Czas oczekiwania domyślnie wynosi 10 sekund, ale dla szybkich operacji zaleca się ustawianie 1–3 sekund, aby przyspieszyć całkowity czas testowania.
XCTWaiter — bardziej elastyczna alternatywa dla wait(for:timeout:). XCTWaiter umożliwia oczekiwanie na wiele oczekiwań, konfigurowanie kolejności wykonywania i obsługę czasu oczekiwania programowo. W przeciwieństwie do wait, XCTWaiter zwraca `XCTWaiter.Result`, który można analizować. Delegat XCTWaiterDelegate powiadamia o naruszeniu kolejności oczekiwań i przekroczeniu czasu.
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")
}
W przykładzie XCTestExpectation jest używane do testowania asynchronicznego logowania. fulfill() wywoływane jest wewnątrz zamknięcia callback-a, sygnalizując, że operacja asynchroniczna została zakończona. Jeśli w ciągu 3 sekund fulfill() nie zostanie wywołany — test pada z timeoutem. Po pomyślnym oczekiwaniu wykonywane są asercje do sprawdzenia wyniku. Wiele oczekiwań można przekazywać jako tablicę i czekać na wykonanie wszystkich.
measure(metrics:) — metoda XCTestCase do tworzenia testów wydajnościowych. Blok kodu wewnątrz measure uruchamiany jest 10 razy z rzędu, a XCTest zbiera statystyki: średni czas, medianę, odchylenie standardowe. Metrics — tablica metryk, które są śledzone: XCTClockMetric (czas), XCTMemoryMetric (pamięć), XCTStorageMetric (dysk) i XCTCPUMetric (procesor). Według Apple WWDC (2024), testy wydajnościowe z XCTCPUMetric są przydatne do wykrywania regresji w algorytmach.
Baseline (linia bazowa) dla testów wydajnościowych ustawiana jest w Xcode Test Plan. Jeśli czas wykonania przekracza baseline o ustalony procent (domyślnie 10%), test uznawany jest za niezaliczony. Baseline jest aktualizowany ręcznie po potwierdzeniu, że zmiana wydajności jest oczekiwana. Test Plan w Xcode umożliwia grupowanie testów wydajnościowych według konfiguracji: debug/release, różne urządzenia, różne wersje iOS.
func testArraySortPerformance() {
let numbers = (1...10000).shuffled()
measure(metrics: [XCTClockMetric()]) {
let _ = numbers.sorted()
}
}
Ten test wydajnościowy mierzy czas sortowania tablicy 10000 elementów. XCTClockMetric rejestruje rzeczywisty czas wykonania. Jeśli po zmianie algorytmu sortowania czas wzrośnie o 10% lub więcej, test wskaże regresję. Testy wydajnościowe XCTest są szczególnie przydatne dla: algorytmów przetwarzania danych, renderowania komponentów UI, operacji na bazie danych i zapytań sieciowych.
Struktura projektu testowego w XCTest podlega konwencji: jeden plik testowy na jedną klasę, umieszczony w osobnej dyrektorii `<TargetName>Tests`. Nazwy plików odpowiadają nazwom testowanych klas z sufiksem `Tests`: `UserService.swift` → `UserServiceTests.swift`. Test Targets w Xcode są konfigurowane osobno dla testów jednostkowych i UI-testów, co umożliwia ich niezależne uruchamianie. Schemes w Xcode zarządzają konfiguracją kompilacji i zestawem uruchamianych testów.
Xcode Cloud i GitHub Actions obsługują uruchamianie XCTest przez `xcodebuild test -scheme App -testPlan SmokeTest`. Pipeline CI obejmuje: kompilacja → uruchomienie testów jednostkowych → uruchomienie UI-testów → publikacja raportu. JUnit report generowany jest przez `xcodebuild` z opcją `-resultBundlePath` i może być zaimportowany do dowolnego narzędzia CI. Code Coverage — wbudowana funkcja XCTest, która pokazuje, które linie kodu są pokryte testami. Minimalny próg pokrycia dla kodu produkcyjnego — 70% dla krytycznej logiki biznesowej.
Xcode Cloud i GitHub Actions obsługują uruchamianie XCTest przez `xcodebuild test -scheme App -testPlan SmokeTest`. Pipeline CI obejmuje: kompilacja → uruchomienie testów jednostkowych → uruchomienie UI-testów → publikacja raportu. JUnit report generowany jest przez `xcodebuild` z opcją `-resultBundlePath` i może być zaimportowany do dowolnego narzędzia CI. Bitrise i Jenkins mają gotowe kroki dla XCTest.
Code Coverage — wbudowana funkcja XCTest, która pokazuje, które linie kodu są pokryte testami. Xcode wyświetla pokrycie na zielono (pokryte), czerwono (niepokryte) i żółto (częściowo pokryte). Minimalny próg pokrycia dla kodu produkcyjnego — 70% dla krytycznej logiki biznesowej. Według Google Testing Blog (2024), wymuszanie 80% pokrycia dla wszystkich modułów prowadzi do pojawienia się „pustych testów”, które nie sprawdzają logiki, a jedynie wykonują kod.
Często zadawane pytania
XCTest — oficjalny framework Apple z bezpośrednią integracją w Xcode. Quick i Nimble — biblioteki stron trzecich, które udostępniają składnię BDD i bardziej czytelne asercje. Quick i Nimble są wygodne dla Acceptance Testing, ale XCTest jest bardziej niezawodny do testów jednostkowych ze względu na brak zewnętrznych zależności.
Kod asynchroniczny testuje się przez XCTestExpectation + `wait(for:timeout:)` lub przez metody `async/await` XCTest (iOS 13+). Dla API opartego na callback tworzy się oczekiwanie, które jest wywoływane w zamknięciu. Dla async/await używa się standardowych asercji w funkcjach async.
XCTest nie zawiera wbudowanego frameworka do mocków. Mockowanie realizuje się przez protokoły: tworzy się klasę mock, implementującą ten sam protokół co rzeczywista zależność. Do automatycznej generacji mocków używa się Cuckoo, SwiftyMocky lub ręcznych mocków. Dependency Injection przez inicjalizatory — obowiązkowy warunek do testowania.
Tak, XCTest uruchamia się na rzeczywistych urządzeniach przez Xcode lub xcodebuild z parametrem `-destination 'platform=iOS,name=iPhone 15'`. UI-testy na rzeczywistych urządzeniach dają dokładniejsze wyniki niż na symulatorach. Do uruchamiania na farmach urządzeń używa się BrowserStack, Sauce Labs lub Firebase Test Lab.
Swift Testing (2024) — nowy framework Apple z makrami `@Test`, `@Suite` i `@Expect`. Udostępnia wbudowaną parametryzację testów, grupowanie w suite-y i bardziej czytelną składnię. Swift Testing współistnieje z XCTest i nie zastępuje go. XCTest pozostaje głównym frameworkiem dla UI-testów i testów wydajnościowych.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również