XCTest: kluczowe pojęcia, klasy XCTestCase i pisanie testów

Autor: IT Sectr Opublikowano: 2026-04-08 Czas czytania: 11 min

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 — oficjalny framework Apple do testowania jednostkowego i UI-testowania w Swift i Objective-C.
  • XCTestCase — klasa bazowa dla wszystkich testów, która udostępnia setUp, tearDown i metody asercji.
  • Asercje — XCTAssertTrue, XCTAssertEqual, XCTAssertNil i inne do sprawdzania oczekiwanych rezultatów.
  • XCTestExpectation — mechanizm do testowania kodu asynchronicznego z oczekiwaniem na wykonanie.
  • Testy wydajnościowe — pomiar czasu wykonania kodu przez metodę measure(metrics:) z progami.

Czym jest XCTest?

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 — klasa bazowa dla testów

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.

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")
    }
}

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.

Asercje w XCTest

Podstawowa grupa asercji

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.

Asercje nil i błędy

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.

AsercjaPrzeznaczeniePrzykład
XCTAssertEqualSprawdzanie równościXCTAssertEqual(a, b)
XCTAssertTrueSprawdzanie prawdyXCTAssertTrue(result)
XCTAssertNilSprawdzanie na nilXCTAssertNil(error)
XCTAssertThrowsErrorSprawdzanie błęduXCTAssertThrowsError(try parse(""))
XCTUnwrapWyodrębnienie optionalXCTUnwrap(value)

XCTestExpectation i testy asynchroniczne

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.

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")
}

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.

Testy wydajnościowe z XCTest

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.

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

Organizacja testów i integracja CI/CD

Struktura projektu testowego

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.

Integracja CI/CD

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

Czym XCTest różni się od Quick i Nimble?

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.

Jak testować kod asynchroniczny w XCTest?

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.

Jak mockować zależności w XCTest?

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.

Czy można uruchamiać XCTest na rzeczywistych urządzeniach?

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.

Co nowego w Swift Testing w porównaniu z XCTest?

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

  • XCTest — oficjalny framework Apple do testowania, wbudowany w Xcode i obsługujący Swift i Objective-C.
  • XCTestCase udostępnia cykl życia setUp/tearDown i zestaw asercji do testów jednostkowych.
  • XCTestExpectation i XCTWaiter — mechanizmy do testowania kodu asynchronicznego z callback i async/await.
  • Testy wydajnościowe używają measure(metrics:) z obsługą XCTClockMetric, XCTMemoryMetric i XCTCPUMetric.
  • Asercje — XCTAssertEqual, XCTAssertTrue, XCTAssertNil, XCTAssertThrowsError, XCTUnwrap i inne.
  • Integracja CI/CD przez xcodebuild, Xcode Cloud i GitHub Actions z automatyczną generacją raportów.
  • Swift Testing — nowy framework Apple, który uzupełnia XCTest do testów jednostkowych i parametryzacji.

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.

Omów projekt

Przeczytaj również