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:
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.
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:
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:
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:
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:
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:
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:
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:
XCTest
Swift Testing
XCTAssertEqual(a, b)
#expect(a == b)
XCTAssertTrue(x)
#expect(x)
XCTAssertNil(x)
#expect(x == nil)
XCTUnwrap(opt)
try #require(opt)
XCTFail("...")
Issue.record("...")
XCTestExpectation
confirmation { ... }
setUp() / tearDown()
init() / deinit
XCTSkip()
throw SkipTest() ili .disabled() trait
Za asinkrone obrasce koji su prije koristili expectations, novi confirmation API je elegantniji:
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
}
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.
Naučite kako napisati vlastite Swift makroe od nule. Vodič pokriva @freestanding i @attached uloge, SwiftSyntax, dijagnostike, testiranje i kada makroe koristiti u Swiftu 6.
SwiftData je Appleov moderni framework za perzistenciju podataka koji zamjenjuje složenost Core Data intuitivnom Swift-nativnom sintaksom. Vodič pokriva @Model, @Query, odnose, #Predicate filtriranje, migraciju sheme i praktične savjete za iOS razvoj.
Kompletni vodič za Swift Concurrency — od async/await osnova i aktora do TaskGroup-a, Sendable protokola i pristupačne konkurentnosti u Swiftu 6.2. S praktičnim primjerima koda.