Swift Testing: Vodič za #expect, #require i parametrizirane testove u Xcode 16

Vodič kroz Swift Testing u Xcode 16: makroi #expect i #require, @Suite organizacija, parametrizirani testovi i postupna migracija s XCTest.

Swift Testing Xcode 16: Vodič (2026)

Ažurirano: 11. lipnja 2026.

Swift Testing je moderni testni framework koji je Apple predstavio uz Swift 6 i Xcode 16 kao deklarativnu zamjenu za XCTest, oslanjajući se na makroe poput #expect i #require umjesto naslijeđivanja klasa i prefiksa test. Umjesto pisanja XCTestCase potklasa, definirate obične funkcije i strukture označene atributom @Test, dok framework nativno podržava paralelno izvršavanje, parametrizirane testove i Swift Concurrency. U ovom vodiču pokazat ću kako postaviti Swift Testing u Xcode 16 projektu, napisati prve testove i migrirati postojeći XCTest kod (s primjerima iz vlastite migracije srednjeg projekta).

  • Swift Testing zahtijeva Xcode 16 ili noviji i Swift 6 toolchain; kompatibilan je s iOS 13+, macOS 10.15+ i Linux platformama.
  • Makro #expect bilježi neuspjehe ali nastavlja izvođenje testa, dok #require prekida test pri prvoj grešci.
  • Testovi se grupiraju u @Suite strukture, a paralelno izvršavanje je uključeno prema zadanim postavkama.
  • Parametrizirani testovi koriste arguments: parametar i jednim deklaracijom pokrivaju desetke ulaznih kombinacija.
  • Swift Testing i XCTest mogu egzistirati u istom targetu, što omogućuje postupnu migraciju velikih test suite-ova.
  • Tagovi i traits poput .disabled(), .tags() i .timeLimit() zamjenjuju ranije atribute i konvencije imenovanja iz XCTest-a.

Što je Swift Testing framework?

Swift Testing je open-source testni framework koji je Apple razvio i objavio u suradnji sa Swift zajednicom putem swiftlang/swift-testing repozitorija na GitHubu. Cilj projekta je bio modernizirati testiranje u Swift ekosustavu. XCTest je naslijeđen iz Objective-C ere i oslanjao se na konvencije imenovanja (svaka metoda mora počinjati sa test), naslijeđivanje od XCTestCase, te pomalo glomazne API-je poput XCTAssertEqual s do osam preopterećenih verzija.

Swift Testing pristupa istom problemu na drugačiji način. Cijeli javni API izgrađen je oko Swift makroa: jedan #expect i jedan #require pokrivaju sve provjere koje su prije zahtijevale desetke XCTAssert* funkcija. Kompajler proširuje izraz unutar makroa, što znači da poruka o grešci automatski prikazuje sve podizraze i njihove vrijednosti. Ne morate ručno pisati "Expected \(a) to equal \(b)". Framework je također duboko integriran sa Swift Concurrency modelom, paralelno izvršava testove prema zadanim postavkama i radi izvan Apple platformi (Linux, Windows).

Prema službenoj Apple dokumentaciji, Swift Testing nije zamjena za UI testove. XCUITest ostaje pravi alat za to. Za unit testove, integracijske testove poslovne logike i testiranje API klijenata, Swift Testing je preporučeni novi izbor.

Postavljanje Swift Testing u Xcode 16

Swift Testing dolazi ugrađen u Xcode 16 i kasnije verzije, pa nije potrebna nikakva dodatna instalacija putem Swift Package Manager-a za većinu slučajeva. Kada kreirate novi test target u Xcode-u, prvi dijalog vas pita izaberete li "Swift Testing" ili "XCTest" predložak. Za nove projekte koji ciljaju iOS 17+ ili macOS 14+ preporučujem Swift Testing kao zadanu opciju.

Ako koristite Swift Package Manager, dodajte testni target u Package.swift:

// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "MyLibrary",
    targets: [
        .target(name: "MyLibrary"),
        .testTarget(
            name: "MyLibraryTests",
            dependencies: ["MyLibrary"]
        )
    ]
)

U test datotekama uvozite modul Testing umjesto XCTest:

import Testing
@testable import MyLibrary

Prvi test s #expect makrom

Najjednostavniji oblik testa u Swift Testing je obična globalna funkcija označena atributom @Test. Nema potrebe za klasama, nasljeđivanjem ni prefiksom imena:

import Testing
@testable import Calculator

@Test func zbrajaPozitivneBrojeve() {
    let kalkulator = Calculator()
    let rezultat = kalkulator.zbroj(2, 3)
    #expect(rezultat == 5)
}

Kada ovaj test pokrenete kroz ⌘U u Xcode-u, makro #expect evaluira izraz. Ako je izraz true, test prolazi. Ako je false, framework automatski generira detaljnu poruku koja uključuje sve podizraze: "Expected `rezultat == 5`, but `rezultat` is `4`". Nije potrebno ručno formatirati poruke o grešci.

Možete dati i prilagođeni opis testa kroz argument atributa:

@Test("Zbrajanje dva pozitivna broja vraća točan zbroj")
func zbrajaPozitivneBrojeve() {
    #expect(Calculator().zbroj(2, 3) == 5)
}

U Test Navigatoru Xcode-a prikazat će se taj prijateljskiji naziv umjesto imena funkcije. Ta kombinacija (bez prefiksa, bez nasljeđivanja, bez ručnih poruka) uklanja oko 30% boilerplate koda u tipičnom test suite-u u usporedbi s XCTest-om. Slično redukciji koju sam opisao u vodiču o Swift Concurrency, makroi ovdje predaju kompajleru posao koji ste prije morali pisati ručno.

Razlika između #require i #expect

Dva glavna makra Swift Testing-a imaju različito ponašanje pri grešci. #expect bilježi neuspjeh i nastavlja izvršavanje testa, dok #require prekida test odmah bacajući internu grešku. Pravilo palca: koristite #require kad ostatak testa nema smisla bez te pretpostavke, a #expect kad želite vidjeti sve neuspjehe u jednom prolazu.

@Test func dekodirajKorisnika() throws {
    let json = #"{"id": 42, "ime": "Ana"}"#.data(using: .utf8)!
    let korisnik = try #require(try? JSONDecoder().decode(Korisnik.self, from: json))

    #expect(korisnik.id == 42)
    #expect(korisnik.ime == "Ana")
    #expect(korisnik.ime.count > 0)
}

U primjeru iznad, ako dekodiranje ne uspije, #require prekida test. Nema smisla provjeravati polja na nil objektu. Ali ako dekodiranje uspije, sva tri #expect poziva će se izvršiti. Ako su sva tri pogrešna, vidjet ćete tri neuspjeha umjesto da popravljate jedan po jedan. Iskreno, to mi je u zadnjoj migraciji uštedjelo dosta vremena oko parsiranja JSON-a.

Posebno koristan oblik je try #require(value) na opcionalu. To je deklarativna zamjena za XCTUnwrap. Makro vraća unwrapped vrijednost ako nije nil, inače prekida test. Tip povrata je ne-opcional, pa kompajler nakon toga zna da varijabla nije nil bez force unwrapa.

Organizacija testova kroz @Suite

Kada želite grupirati povezane testove i dijeliti setup kod, koristite strukturu označenu atributom @Suite. Za razliku od XCTestCase klasa koje su zahtijevale referentne tipove, Swift Testing-ov suite je obična struct ili actor, što daje bolju thread-safety semantiku:

@Suite("Validacija korisničkog unosa")
struct ValidacijaTests {
    let validator: EmailValidator

    init() {
        self.validator = EmailValidator()
    }

    @Test func prihvacaValjanEmail() {
        #expect(validator.isValid("[email protected]"))
    }

    @Test func odbijaEmailBezDomene() {
        #expect(!validator.isValid("ana@"))
    }

    @Test func odbijaPrazanString() {
        #expect(!validator.isValid(""))
    }
}

Framework kreira novu instancu suite-a za svaki test, pa init služi kao setup, a deinit kao teardown. Nema više setUp() i tearDown() metoda. Koristite obične Swift inicijalizatore. Suites se mogu i ugnijezditi kako biste organizirali velike testne hijerarhije.

Paralelizam je uključen prema zadanim postavkama: dva testa unutar istog suite-a će se izvršavati paralelno. Ako vam to ne odgovara (npr. dijele stanje datotečnog sustava), označite suite serijskim:

@Suite("Testovi koji dijele bazu", .serialized)
struct BazaTests {
    @Test func upisuje() { /* ... */ }
    @Test func cita() { /* ... */ }
}

Parametrizirani testovi s argumentima

Jedna od najvećih praktičnih prednosti Swift Testing-a su parametrizirani testovi. U XCTest-u ste morali ili pisati for petlju unutar jednog testa (loši izvještaji) ili duplicirati metode. Sada predajete kolekciju argumenata atributu:

@Test(arguments: [
    ("[email protected]", true),
    ("ana@primjer", false),
    ("@primjer.hr", false),
    ("[email protected].", false),
    ("", false)
])
func validiraEmail(unos: String, ocekivano: Bool) {
    let validator = EmailValidator()
    #expect(validator.isValid(unos) == ocekivano)
}

Xcode će prikazati svaki argument kao zasebni test u Test Navigatoru. Ako jedan slučaj padne, izvještaj točno pokazuje koja je kombinacija uzrok problema. Možete predati i više nezavisnih kolekcija koje se kombiniraju kartezijevim umnoškom:

@Test(arguments: [1, 2, 3], ["a", "b"])
func kombinacije(broj: Int, slovo: String) {
    #expect(broj > 0)
    #expect(!slovo.isEmpty)
}

Ovaj test pokrenut će se šest puta s kombinacijama (1,"a"), (1,"b"), (2,"a"), .... Ako želite samo paralelne parove (1×3 = 3 izvršavanja), koristite zip() nad ulaznim nizovima. Za detaljnu analizu trade-offa između parametrizacije i fixture datoteka, pogledajte vodič o SwiftData perzistenciji gdje sam pisao o sličnom obrascu za seed podatke.

Testiranje async koda i konkurencije

Swift Testing je dizajniran nakon Swift Concurrency, pa async i throws rade prirodno. Nije potreban XCTestExpectation nikada više. Dodajte ključne riječi funkciji i pišite kod kako biste ga pisali u produkciji:

@Test func dohvacaKorisnikaSaServera() async throws {
    let klijent = APIKlijent(host: "https://api.primjer.hr")
    let korisnik = try await klijent.dohvatiKorisnika(id: 42)

    #expect(korisnik.id == 42)
    try #require(korisnik.email.contains("@"))
}

Za testiranje aktera (actors) i izoliranog stanja, framework radi s @MainActor i prilagođenim globalnim akterima bez problema. Možete označiti pojedinačni test ili cijeli suite kao izoliran:

@Test @MainActor
func azuriraUIstanjePrilikomDohvata() async throws {
    let viewModel = ProfilViewModel()
    try await viewModel.ucitajProfil()
    #expect(viewModel.stanje == .ucitan)
}

Za vremenske limite na pojedinim testovima, koristite trait .timeLimit():

@Test(.timeLimit(.minutes(1)))
func dugaOperacija() async throws {
    try await procesirajVelikiSkupPodataka()
}

Migracija s XCTest na Swift Testing

Dobra vijest za postojeće projekte: Swift Testing i XCTest mogu koegzistirati u istom test targetu. Ne morate migrirati sve odjednom. Apple preporučuje postupnu migraciju modula po modula, počevši od najnovijih testova koji su najmanje zapleteni s legacy XCTest API-jima. U mojem posljednjem projektu migrirao sam najprije domain layer testove, a UI-vezane fixture-e ostavio za kasnije. Funkcioniralo je bez većih neugodnosti.

Mapiranje između XCTest i Swift Testing API-ja:

XCTestSwift Testing
XCTAssertEqual(a, b)#expect(a == b)
XCTAssertTrue(x)#expect(x)
XCTAssertNil(x)#expect(x == nil)
XCTUnwrap(opt)try #require(opt)
XCTFail("...")Issue.record("...")
XCTestExpectationconfirmation { ... }
setUp() / tearDown()init() / deinit
XCTSkip()throw SkipTest() ili .disabled() trait

Za asinkrone obrasce koji su prije koristili expectations, novi confirmation API je elegantniji:

@Test func emitirajDogadjaje() async {
    await confirmation("Emitirano 3 događaja", expectedCount: 3) { potvrda in
        let emitter = EventEmitter()
        emitter.onEvent = { _ in potvrda() }
        emitter.start()
        try? await Task.sleep(for: .seconds(1))
    }
}

Apple je u WWDC24 sesiji "Meet Swift Testing" objavio da XCTest neće biti depreciran niti za jednu deklariranu Xcode verziju. Možete sigurno održavati postojeće testove godinama. Migracija je preporuka za bolji developer experience, ne hitnost.

Tagovi, uvjeti i traits

Traits su modifikatori koji se predaju atributu @Test ili @Suite kako bi promijenili ponašanje. Najkorisniji su tagovi, uvjetna onemogućavanja i bug tracking poveznice. Najprije definirate tagove na razini modula:

extension Tag {
    @Tag static var integracijski: Self
    @Tag static var spori: Self
    @Tag static var mreza: Self
}

Zatim ih primjenjujete na testove:

@Test(.tags(.integracijski, .mreza))
func dohvacaProdukcijskeAPI() async throws { /* ... */ }

@Test(.disabled("Čeka popravak za bug FB12345"))
func neispravniSlucaj() { /* ... */ }

@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
func samoNaCIServeru() { /* ... */ }

@Test(.bug("https://github.com/org/repo/issues/42", "Reprodukcija crasha"))
func regresijaIssue42() { /* ... */ }

U Xcode Test Reportu možete filtrirati po tagovima, pa pokrenete samo brze testove tijekom razvoja, a integracijske spremite za CI pipeline. Time se nadomještaju krhke konvencije imenovanja (test_slow_ prefiksi) i strukturira test suite na način koji preživljava refaktoriranje. Slično tome, @Observable makro u Observation frameworku donosi sličnu deklarativnost u runtime svijet: manje boilerplate-a, više namjere u kodu.

Često postavljana pitanja

Treba li mi Xcode 16 za korištenje Swift Testing frameworka?

Da, Swift Testing zahtijeva Xcode 16 ili noviji jer ovisi o Swift 6 makro infrastrukturi. Postoji backport za Swift 5.9+ kroz swift-testing Swift Package, ali za potpunu Xcode integraciju i Test Navigator podršku preporučuje se Xcode 16+ s deployment targetom iOS 13 ili kasnijim.

Mogu li koristiti Swift Testing i XCTest u istom projektu?

Da, oba frameworka mogu egzistirati u istom test targetu bez konflikta. Xcode će ih obje izvršiti tijekom ⌘U, a izvještaji su objedinjeni. To omogućuje postupnu migraciju gdje pišete nove testove u Swift Testing-u, a postojeće XCTest testove ostavljate netaknute dok ne dođe vrijeme za refaktoriranje.

Zašto nema metoda setUp i tearDown u Swift Testing?

Swift Testing kreira novu instancu suite strukture za svaki test, pa init služi kao setup, a deinit kao teardown. To je idiomatičniji Swift pristup koji koristi ARC umjesto framework callback-ova i osigurava izolaciju stanja između testova bez ručnog resetiranja varijabli.

Kako testirati greške koje funkcija baca u Swift Testing?

Koristite #expect(throws:) overload za provjeru da kod baca određeni tip ili konkretnu instancu greške. Na primjer: #expect(throws: NetworkError.timeout) { try await klijent.dohvati() }. Možete i provjeriti samo da bilo koja greška bude bačena s #expect(throws: (any Error).self).

Podržava li Swift Testing UI testove iOS aplikacija?

Ne, za UI testove i dalje koristite XCUITest. Swift Testing je namijenjen unit i integracijskim testovima poslovne logike, modela i API klijenata. XCUITest ostaje preporučeni alat za automatizaciju korisničkog sučelja jer ovisi o XCTest infrastrukturi za snimanje koraka i pristup elementima sučelja.

Editorial Team
O Autoru Editorial Team

Our team of expert writers and editors.