Swift Testing: Kompletní průvodce novým testovacím frameworkem pro Xcode 16+ (2026)
Naučte se psát unit testy ve Swift Testing v Xcode 16+. Makra @Test a #expect, parametrizované testy, souběžnost a migrace z XCTest s praktickými ukázkami kódu.
Swift Testing je nový open-source testovací framework od Applu, který v Xcode 16 a novějších nahrazuje XCTest jako doporučený způsob psaní unit testů ve Swiftu. Místo dědění z XCTestCase používá makra @Test a #expect, plně využívá strict concurrency Swiftu 6 a podporuje parametrizované testy, traits, tagy i paralelní spouštění. V tomto průvodci se naučíte framework nastavit, napsat první test, postupně přejít z XCTest a využít pokročilé funkce, které dřív vyžadovaly externí knihovny.
Swift Testing je integrovaný v Xcode 16+ a Swift 6.0+. Pro produkční použití Apple doporučuje Xcode 16.2 a novější.
Makro @Test nahrazuje konvenci test* metod a #expect nahrazuje rodinu XCTAssert* funkcí.
Framework nativně podporuje async/await, parametrizované testy přes arguments: a paralelní spouštění bez extra konfigurace.
XCTest i Swift Testing můžou koexistovat ve stejném testovacím cíli, takže migrace probíhá postupně.
Traits jako .tags, .disabled, .bug a .timeLimit nahrazují většinu mechanismů, které dřív vyžadovaly podtřídy nebo externí balíčky.
Suite typy (@Suite) umožňují seskupovat testy a sdílet stavovou logiku přes init a deinit místo setUp/tearDown.
Co je Swift Testing a proč nahrazuje XCTest?
Swift Testing je open-source testovací framework, který Apple oficiálně představil na WWDC 2024 jako nástupce XCTest. Cílem bylo poskytnout API navržené přímo pro moderní Swift, tedy strukturovanou souběžnost, makra, generika s primary associated types a strict concurrency checking. Místo dědění z XCTestCase stačí libovolnou globální nebo statickou funkci označit makrem @Test; runner si ji najde sám.
Klíčový rozdíl oproti XCTest je v expresivitě. Místo desítek specializovaných assert funkcí (XCTAssertEqual, XCTAssertNotNil, XCTAssertGreaterThan…) máte dvě makra: #expect a #require. Obě přijímají libovolný booleovský výraz a při selhání pomocí informací z makra vypíší rozšířenou diagnostiku. Uvidíte konkrétní hodnoty operandů, ne jen "expected true, got false". Test runner běží paralelně už ve výchozím stavu, takže testy musí být thread-safe. Tohle mimochodem chytí celou třídu chyb, které XCTest před vývojářem schovával.
Framework je distribuovaný jako Swift Package, ale v Xcode 16+ je integrovaný přímo v sadě nástrojů. Funguje na Apple platformách, Linuxu i Windows a podporuje jej i swift test z příkazové řádky. Pro existující kódovou základnu je dobrá zpráva, že nemusíte přepisovat všechno najednou. Swift Testing a XCTest se v jednom cíli vzájemně tolerují.
Instalace a nastavení v Xcode 16+
Pokud používáte Xcode 16.2 nebo novější, Swift Testing máte k dispozici bez jakékoli další instalace. Nový test target vytvořený přes File → New → Target → Unit Testing Bundle automaticky obsahuje šablonu používající @Test místo původní XCTestCase šablony. Vygenerovaný soubor vypadá takto:
Pro Swift Package projekty stačí přidat závislost ve Package.swift. V SDK Xcode 16+ je framework součástí toolchainu, takže pro lokální build není třeba žádný dependency záznam. Stačí importovat modul Testing v test souborech:
// Package.swift — pro Linux nebo starší toolchainy
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "MyLibrary"),
.testTarget(
name: "MyLibraryTests",
dependencies: ["MyLibrary"]
)
]
)
Testy spustíte standardně přes ⌘U v Xcode nebo příkazem swift test z terminálu. Výsledky se v Xcode 16+ zobrazují v upraveném Test Navigator panelu, který rozlišuje mezi @Test funkcemi, @Suite typy a parametrizovanými iteracemi. Pokud chcete framework použít v existujícím projektu, který stále cílí na starší Swift, doplňte swift-testing jako Swift Package závislost z oficiálního swiftlang/swift-testing repozitáře.
Jak napsat první test s makrem @Test
Nejjednodušší test je obyčejná funkce označená makrem @Test. Funkce nemusí být součástí žádné třídy a může (ale nemusí) být async nebo throws. Příklad jednotkového testu pro funkci, která validuje e-mailovou adresu:
Argument v @Test("…") je volitelný a slouží jako lidsky čitelný popis, který se zobrazí v Test Navigatoru a v reportu. Pokud jej vynecháte, runner použije název funkce. Důležitý detail: #expect nezastavuje běh testu při selhání. Pokud máte několik nezávislých kontrol, všechny se vyhodnotí a uvidíte kompletní seznam toho, co neprošlo. To je výrazný rozdíl oproti XCTAssert, kde testy sice taky pokračovaly, ale bez tak detailní diagnostiky.
Diagnostika selhaného #expect je nejviditelnější vylepšení frameworku. Když napíšete #expect(user.age == 18) a hodnota je 17, výstup obsahuje doslova "Expectation failed: user.age == 18 → 17 == 18". Žádné psaní vlastních messages argumentů. Pro pochopení vnitřního fungování makra doporučuji projít oficiální dokumentaci modulu Testing na Apple Developer.
Rozdíl mezi #expect a #require
Framework nabízí dvě hlavní kontrolní makra a v praxi je strašně důležité rozumět, kdy použít které. #expect zaznamená selhání a běh testu pokračuje dál. #require selhání vyhodí jako error a další kód v testu se neprovede. Z toho plyne, že test musí být buď throws, nebo musíte try obalit do do/catch bloku.
@Test func loadsUserProfile() async throws {
let response = try await api.fetchProfile(id: 42)
// Pokud je odpověď nil, nemá smysl pokračovat
let profile = try #require(response.body)
// Tyto kontroly proběhnou jen pokud profile existuje
#expect(profile.name == "Anna")
#expect(profile.age >= 18)
}
Pravidlo, které se mi osvědčilo: pokud následující řádky bez vyhodnocené hodnoty zhavarují nebo nedávají smysl, použijte #require. Pokud jde o nezávislou kontrolu vlastnosti, použijte #expect. Honestly, na minulém projektu jsem si přeházel pořadí těchto dvou maker a nakonec byl test "zelený", přestože druhá kontrola vůbec nedoběhla. Mimochodem #require vrací unwrapnutou hodnotu, funguje analogicky jako guard let, ale uvnitř testu. To vede k čistšímu kódu, než býval v XCTest s XCTUnwrap.
Parametrizované testy přes arguments
Parametrizované testy patří k nejvíc požadovaným funkcím, které XCTest nikdy nepřinesl. Ve Swift Testing stačí makru @Test předat parametr arguments: s libovolnou sekvencí a runner vygeneruje samostatnou iteraci pro každou hodnotu. Každá iterace má vlastní řádek v reportu a může selhat samostatně.
Pokud potřebujete zkombinovat dva nezávislé seznamy, předejte dvě pole. Framework automaticky vytvoří kartézský součin, což je užitečné pro testování permutací (např. všechny kombinace lokalizace × téma):
Iterace běží paralelně, takže si dejte pozor na sdílený mutable stav. Pokud vstupní data závisejí na čase nebo síti, použijte staticky známý seznam. Generování dat až v běhu testu znesnadňuje reprodukovatelnost selhání.
Suite, fixtures a sdílení stavu
Když potřebujete sdílet logiku mezi více testy, použijte typ označený @Suite. Suite je obyčejný struct, actor nebo class, jehož init() nahrazuje setUp() a deinit nahrazuje tearDown(). Pro každý test runner vytvoří novou instanci suite, což eliminuje sdílení stavu mezi testy, tedy nejčastější zdroj flaky testů v XCTest.
Suite se dají vnořovat a vnitřní @Suite sdědí traits z rodiče. Pokud potřebujete sdílet drahý zdroj přes všechny testy v suite (např. databázové schéma), zvažte použití static let vlastnosti. Buďte si ale vědomi, že je sdílený napříč paralelními iteracemi a musí být thread-safe. Pro práci s persistentním úložištěm v testech je dobré tento přístup zkombinovat s tipy v našem průvodci SwiftData od základů po pokročilé techniky.
Asynchronní a souběžné testy
Swift Testing byl od začátku navržen pro async/await. Stačí, aby test funkce byla async, a uvnitř můžete přímo používat await bez nutnosti expectations a fulfillů, které vyžadoval XCTest. To výrazně zkracuje kód i mentální zátěž.
Pro testování událostí v čase (např. že observable hodnota dostane konkrétní sekvenci aktualizací) má framework confirmation, který nahrazuje XCTestExpectation. Confirmation lze vyvolat několikrát a testem můžete přesně specifikovat očekávaný počet. Runner selže, pokud počet nesedí.
@Test func emitsThreeProgressUpdates() async throws {
try await confirmation(expectedCount: 3) { progressed in
let downloader = Downloader()
downloader.onProgress = { _ in progressed() }
try await downloader.run()
}
}
Pokud používáte Swift 6.2 a chcete pochopit, jak Swift Testing zapadá do nového souběžnostního modelu, mrkněte i na článek o Approachable Concurrency ve Swiftu 6.2. Pomůže vám vyhnout se data race upozorněním přímo v testovacím kódu.
Traits, tagy a paralelní spouštění
Traits jsou metadata, která modifikují chování konkrétního testu nebo celé suite. Předávají se jako další argumenty makra @Test nebo @Suite a nahrazují většinu věcí, které jste dřív dělali přes podtřídy nebo přepisování defaultTestSuite. Mezi nejčastější patří:
.disabled("důvod"), dočasně vyřadí test bez jeho smazání
.bug("FB1234567"), propojí test s konkrétním záznamem v bug trackeru
.tags(.integration), přidělí testu uživatelský tag pro filtrování
.timeLimit(.minutes(1)), selže, pokud test trvá déle než limit
.serialized, vynutí sekvenční běh všech iterací suite
extension Tag {
@Tag static var integration: Self
@Tag static var slow: Self
}
@Test(.tags(.integration, .slow), .timeLimit(.minutes(2)))
func endToEndCheckoutFlow() async throws {
let app = TestApp()
try await app.launch()
try await app.completeCheckout()
}
V Xcode 16+ se dá v Test Plan filtrem zvolit jen testy s konkrétním tagem. To znamená, že integrační běh v CI může spustit jen .integration testy a smoke test po commitu jen netagované rychlé testy. Pro CI je užitečná i oficiální vize frameworku, která dokumentuje plánovaný roadmap včetně lepší integrace s reporting nástroji.
Srovnání Swift Testing a XCTest
Než se rozhodnete pro migraci, vyplatí se porovnat hlavní dimenze obou frameworků. Následující tabulka shrnuje nejčastější srovnávací body:
Vlastnost
XCTest
Swift Testing
Definice testu
Metoda začínající test v XCTestCase
Funkce s makrem @Test
Assertion API
~30 specifických funkcí (XCTAssertEqual…)
Dvě makra: #expect a #require
Async/await
Vyžadovalo expectations
Nativní podpora
Parametrizované testy
Nedostupné bez externí knihovny
Vestavěné přes arguments:
Paralelní spouštění
Process-based, opt-in
In-process, výchozí
Setup / Teardown
setUp() / tearDown()
init() / deinit
Filtrace
Podle názvu
Podle tagů a traits
Diagnostika selhání
"expected X, got Y"
Plný výraz s expandovanými hodnotami
UI testy
Plně podporované
Zatím doporučeno XCTest
XCTest stále zůstává správnou volbou pro UI testy přes XCUIApplication a pro performance testy přes measure bloky. Swift Testing tyto domény zatím nepokrývá. Pro běžné unit testy je ovšem Swift Testing v drtivé většině případů lepší volbou už dnes.
Migrace z XCTest krok za krokem
Doporučený postup je nemigrovat všechno najednou. Swift Testing a XCTest můžou koexistovat ve stejném test targetu, takže lze přepisovat suite po suite a tlak na CI minimalizovat. Když jsem tohle nedávno dělal na vlastní aplikaci, ušetřilo mi to dva dny chaosu v zelené větvi. Praktický workflow vypadá takto:
Přidejte import Testing do nového souboru a v něm napište jeden nový test s @Test. Ověřte, že běží.
Vyberte jednu menší XCTestCase třídu a přepište ji na @Suite struct. setUp() přesuňte do init(), tearDown() do deinit.
Nahraďte XCTAssertEqual(a, b) za #expect(a == b), XCTAssertNil(x) za #expect(x == nil) atd. Většinu se to dá udělat regexem.
XCTUnwrap(value) nahraďte za try #require(value). Nezapomeňte funkci označit throws.
Pro asynchronní testy odstraňte XCTestExpectation a wait(for:timeout:) bloky. Funkci převeďte na async throws a použijte přímo await.
Pokud měla třída smyčku přes pole vstupů (for input in inputs { test… }), přepište ji na jediný test s arguments: inputs.
Spusťte celý test plan v Xcode i v CI a ověřte, že počty testů odpovídají očekávání.
Většina migrací běžných assert funkcí je mechanická. Komplikovanější bývají testy, které spoléhají na XCTestObservation nebo dynamicky vytvářené test cases. V těchto případech může být jednodušší nechat je v XCTest, dokud Swift Testing nepokryje váš konkrétní use case.
Nejčastější problémy a jejich řešení
Při přechodu na Swift Testing narazíte typicky na pár opakujících se problémů. Data race upozornění v testech: protože framework spouští testy paralelně už ve výchozím stavu, kód, který v XCTest "fungoval", může najednou hlásit chyby strict concurrency checkeru. Řešením je buď udělat sdílený stav Sendable, nebo pro konkrétní suite použít .serialized trait. (Tenhle bug jsem chytil přesně při shipnutí jedné CI sady a byl pěkně schovaný.)
Test, který v XCTest procházel, ve Swift Testing selhává: nejčastěji proto, že XCTest tiše padl na sekundární assert, ale Swift Testing nyní vypíše všechny problémy najednou. Pozorně si přečtěte celý report. Pravděpodobně najdete latentní bug, který byl v kódu už dříve. Pomalé spouštění testů: pokud máte stovky parametrizovaných iterací, framework pro každou vytvoří novou suite instanci. Pokud je init() drahý (např. načítá z disku), přesuňte jednorázové fixtures do static let a inicializujte je líně.
Mock objekty s neisolated mutable state: starší mock knihovny počítaly se sekvenčním během. Pokud používáte vlastní mock a vidíte občasné selhání, obalte interní stav do actor nebo přidejte NSLock. Pro inspiraci na práci se Sendable typy doporučuji článek Bezpečnost dat ve Swift 6.
Často kladené otázky
Nahradí Swift Testing úplně XCTest?
Zatím ne. Pro unit testy je Swift Testing v Xcode 16+ doporučená volba, ale XCTest zůstává jediný framework s plnou podporou UI testů (XCUIApplication) a performance měření (measure). Apple plánuje obě domény postupně přesunout, ale konkrétní termín zatím nezveřejnil.
Lze Swift Testing použít v projektu pro iOS 15?
Ano. Framework závisí jen na verzi Swift kompilátoru (5.10+), ne na konkrétním deployment targetu. Můžete tedy psát Swift Testing testy i pro aplikaci, která cílí na iOS 15. Testy běží na vašem Macu s aktuálním toolchainem, nikoli na cílovém OS.
Jak spustit jen testy s konkrétním tagem?
V Xcode otevřete Test Plan, přidejte sekci Selected Tests a vyberte filtr podle tagu. Z příkazové řádky použijte swift test --filter "tag:integration". Tagy se definují jako statické vlastnosti na Tag a označují se přes trait .tags(.integration).
Proč se mé #expect neukončuje test při selhání?
Protože tak je makro navržené. #expect zaznamená selhání a běh testu pokračuje, aby vás informoval o všech problémech najednou. Pokud potřebujete tvrdé ukončení (např. když následující kód nemá smysl bez splněné podmínky), použijte try #require(...) místo #expect.
Jak migrovat XCTestExpectation na Swift Testing?
Pro testy s asynchronním kódem ve většině případů stačí převést funkci na async throws a použít přímo await. Pro očekávané vícenásobné události použijte confirmation(expectedCount:), který selže, pokud se callback zavolá jiný počet krát, než jste deklarovali.
Priya spent six years at Instacart building the iOS shopper app, where she led the migration from UIKit to SwiftUI across 80+ screens and cut crash-free sessions from 99.2% to 99.87%. Before that, she was a contractor at a Bay Area design studio shipping App Store apps for two Fortune 500 retail clients.
She focuses on practical SwiftUI architecture - what holds up when you have 12 engineers committing to the same codebase, not just toy MVVM examples. Her recent work involves The Composable Architecture, Swift concurrency migration audits, and reducing main-thread hangs on older devices like the iPhone XR that enterprise fleets still ship.
Priya runs a small consultancy in Oakland and occasionally speaks at try! Swift NYC. She has been writing Swift since the Objective-C bridging days of 2015.
Kompletní průvodce migrací z ObservableObject na @Observable makro v SwiftUI. Reálná čísla výkonu, rozdíly mezi @State a @StateObject, použití @Bindable a typické chyby, které vám ušetří hodiny ladění v iOS 26.
Nativní SwiftUI WebView v iOS 26 konečně řeší zobrazení webového obsahu bez UIKit wrapperů. Naučte se používat WebView a WebPage pro načítání stránek, spouštění JavaScriptu, řízení navigace a vlastní URL schémata.
Swift 6.2 přináší Approachable Concurrency — váš kód je ve výchozím stavu jednovláknový na @MainActor. Naučte se používat @concurrent, postupnou migraci a nové příznaky kompilátoru s funkčními příklady.