# Swift Testing Framework: XCTest'in Geleceği
Apple, WWDC24'te tanıttığı Swift Testing framework'ü ile test yazım deneyimini kökten değiştiriyor. Macro tabanlı syntax, parametrize testler, tag sistemi ve Swift concurrency entegrasyonu ile XCTest'in yerini almaya aday. Bu rehberde Swift Testing'in tüm özelliklerini, XCTest'ten farkları ve migration stratejisini ele alacağız.
İçindekiler
- Swift Testing vs XCTest
- @Test Macro
- #expect ve #require
- Parameterized Tests
- Test Suites
- Tags & Organization
- Async Testing
- Traits & Configuration
- XCTest Migration
- CI/CD Integration
- Best Practices
1. Swift Testing vs XCTest
Özellik | Swift Testing | XCTest |
|---|---|---|
**Syntax** | `@Test` macro | `func test...` naming |
**Assertions** | `#expect`, `#require` | `XCTAssert*` |
**Parameterized** | Native destek | Manuel loop |
**Async** | First-class | `async` + `XCTestExpectation` |
**Tags** | `@Tag` | Scheme filter |
**Suite** | `@Suite` struct/class | `XCTestCase` class |
**Paralel** | Varsayılan paralel | Sıralı |
**Hata mesajı** | Zengin, detaylı | Temel |
**Platform** | Apple + Linux + Windows | Apple only |
İlk Test
swift
1// XCTest (eski)2import XCTest3 4class MathTests: XCTestCase {5 func testAddition() {6 XCTAssertEqual(2 + 2, 4)7 }8}9 10// Swift Testing (yeni)11import Testing12 13@Test func addition() {14 #expect(2 + 2 == 4)15}Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
2. @Test Macro
swift
1import Testing2 3// Basit test4@Test func userCreation() {5 let user = User(name: "Ali", email: "[email protected]")6 #expect(user.name == "Ali")7 #expect(user.isValid)8}9 10// Display name ile11@Test("Kullanıcı e-posta doğrulaması başarılı olmalı")12func emailValidation() {13 let validator = EmailValidator()14 #expect(validator.isValid("[email protected]"))15}16 17// Disabled test18@Test(.disabled("API henüz hazır değil"))19func apiIntegration() {20 // Bu test çalıştırılmaz21}22 23// Conditional test24@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))25func onlyCITest() {26 // Sadece CI ortamında çalışır27}28 29// Bug referansı30@Test(.bug("https://github.com/org/repo/issues/42", "Login timeout fix"))31func loginTimeout() {32 // Bug ile ilişkili test33}34 35// Zaman limiti36@Test(.timeLimit(.minutes(2)))37func longRunningOperation() async {38 await heavyComputation()39}3. #expect ve #require
swift
1import Testing2 3@Test func assertions() {4 let numbers = [1, 2, 3, 4, 5]5 6 // #expect — test devam eder (soft assertion)7 #expect(numbers.count == 5)8 #expect(numbers.contains(3))9 #expect(!numbers.isEmpty)10 11 // Optional check12 let name: String? = "Ali"13 #expect(name != nil)14 15 // Error bekleme16 #expect(throws: ValidationError.invalidEmail) {17 try validateEmail("invalid")18 }19 20 // Herhangi bir error bekleme21 #expect(throws: (any Error).self) {22 try riskyOperation()23 }24 25 // Error fırlatMAMASI bekleme26 #expect(throws: Never.self) {27 try safeOperation()28 }29}30 31@Test func requireDemo() {32 let data: Data? = loadTestData()33 34 // #require — başarısız olursa test DURUR (hard assertion)35 let unwrapped = try #require(data)36 // data nil ise buradan sonrası çalışmaz37 38 let json = try #require(JSONSerialization.jsonObject(with: unwrapped) as? [String: Any])39 #expect(json["status"] as? String == "success")40}Assertion Karşılaştırması
Swift Testing | XCTest | Davranış |
|---|---|---|
`#expect(a == b)` | `XCTAssertEqual(a, b)` | Soft, test devam |
`#expect(throws:)` | `XCTAssertThrowsError` | Error bekleme |
`try #require(x)` | `XCTUnwrap(x)` | Nil ise durdur |
`#expect(a > b)` | `XCTAssertGreaterThan` | Karşılaştırma |
4. Parameterized Tests
Swift Testing'in en güçlü özelliklerinden biri:
swift
1// Basit parameterized test2@Test(arguments: ["[email protected]", "[email protected]", "[email protected]"])3func validEmails(email: String) {4 #expect(EmailValidator.isValid(email))5}6 7// İki parametre8@Test(arguments: [1, 2, 3], [10, 20, 30])9func multiplication(a: Int, b: Int) {10 #expect(a * b == a * b) // 9 kombinasyon test edilir11}12 13// Zip ile eşleştirme (1:1)14@Test(arguments: zip(15 ["[email protected]", "invalid", "[email protected]"],16 [true, false, false]17))18func emailValidation(email: String, expected: Bool) {19 #expect(EmailValidator.isValid(email) == expected)20}21 22// Enum case'leri23enum UserRole: CaseIterable { case admin, editor, viewer }24 25@Test(arguments: UserRole.allCases)26func rolePermissions(role: UserRole) {27 let user = User(role: role)28 #expect(user.canRead) // Tüm roller okuyabilir29}30 31// Custom type32struct LoginTestCase: CustomTestStringConvertible {33 let email: String34 let password: String35 let shouldSucceed: Bool36 37 var testDescription: String {38 "\(email) → \(shouldSucceed ? "success" : "failure")"39 }40}41 42@Test(arguments: [43 LoginTestCase(email: "[email protected]", password: "Str0ng!Pass", shouldSucceed: true),44 LoginTestCase(email: "[email protected]", password: "weak", shouldSucceed: false),45 LoginTestCase(email: "invalid", password: "Str0ng!Pass", shouldSucceed: false),46])47func loginValidation(testCase: LoginTestCase) {48 let result = AuthService.validate(email: testCase.email, password: testCase.password)49 #expect(result == testCase.shouldSucceed)50}5. Test Suites
swift
1// Struct tabanlı suite (XCTestCase yerine)2@Suite("Kullanıcı Servisi Testleri")3struct UserServiceTests {4 let service = UserService()5 let mockRepo = MockUserRepository()6 7 init() {8 // Her test öncesi çalışır (setUp yerine)9 service.repository = mockRepo10 }11 12 @Test func createUser() async throws {13 let user = try await service.create(name: "Ali", email: "[email protected]")14 #expect(user.id != nil)15 }16 17 @Test func deleteUser() async throws {18 let user = try await service.create(name: "Test", email: "[email protected]")19 try await service.delete(user.id!)20 #expect(await service.find(user.id!) == nil)21 }22 23 // İç içe suite24 @Suite("Doğrulama Testleri")25 struct ValidationTests {26 @Test func emptyName() {27 #expect(throws: ValidationError.emptyName) {28 try User.validate(name: "", email: "[email protected]")29 }30 }31 }32}6. Tags & Organization
swift
1// Tag tanımlama2extension Tag {3 @Tag static var critical: Self4 @Tag static var slow: Self5 @Tag static var network: Self6 @Tag static var ui: Self7 @Tag static var regression: Self8}9 10// Tag kullanımı11@Test(.tags(.critical, .network))12func apiAuthentication() async {13 // Kritik network testi14}15 16@Test(.tags(.slow))17func largeDataProcessing() {18 // Yavaş test19}20 21// Xcode'da filtreleme:22// swift test --filter "critical"23// swift test --skip "slow"7. Async Testing
swift
1@Test func asyncDataFetch() async throws {2 let service = DataService()3 let result = try await service.fetchUsers()4 #expect(!result.isEmpty)5 #expect(result.count > 0)6}7 8// Confirmation (XCTestExpectation yerine)9@Test func notificationReceived() async {10 await confirmation("Bildirim alındı") { confirm in11 let observer = NotificationCenter.default.addObserver(12 forName: .dataDidUpdate, object: nil, queue: .main13 ) { _ in14 confirm() // expectation.fulfill() yerine15 }16 17 // Bildirimi tetikle18 DataManager.shared.refresh()19 20 // cleanup21 NotificationCenter.default.removeObserver(observer)22 }23}24 25// Birden fazla confirmation26@Test func multipleEvents() async {27 await confirmation("Event received", expectedCount: 3) { confirm in28 let emitter = EventEmitter()29 emitter.onEvent = { confirm() }30 emitter.emit(.a)31 emitter.emit(.b)32 emitter.emit(.c)33 }34}8. Traits & Configuration
swift
1// Serialized execution (paralel değil, sıralı)2@Suite(.serialized)3struct DatabaseTests {4 @Test func insertRecord() { }5 @Test func queryRecords() { } // insert'ten sonra çalışır6 @Test func deleteRecord() { } // query'den sonra çalışır7}8 9// Custom trait10struct MemoryIntensive: TestTrait, SuiteTrait {11 // Test veya suite'e metadata ekler12}13 14extension TestTrait where Self == MemoryIntensive {15 static var memoryIntensive: Self { MemoryIntensive() }16}17 18@Test(.memoryIntensive)19func processLargeFile() {20 // Bellek yoğun test21}9. XCTest'ten Migration
swift
1// ÖNCE (XCTest)2import XCTest3 4class CartTests: XCTestCase {5 var cart: ShoppingCart!6 7 override func setUp() {8 cart = ShoppingCart()9 }10 11 override func tearDown() {12 cart = nil13 }14 15 func testAddItem() {16 cart.add(item: .init(name: "Kitap", price: 29.99))17 XCTAssertEqual(cart.items.count, 1)18 XCTAssertEqual(cart.total, 29.99, accuracy: 0.01)19 }20 21 func testEmptyCart() {22 XCTAssertTrue(cart.isEmpty)23 XCTAssertEqual(cart.total, 0)24 }25}26 27// SONRA (Swift Testing)28import Testing29 30@Suite("Alışveriş Sepeti")31struct CartTests {32 let cart = ShoppingCart() // init = setUp33 34 @Test("Ürün ekleme")35 func addItem() {36 var cart = cart // struct kopyası37 cart.add(item: .init(name: "Kitap", price: 29.99))38 #expect(cart.items.count == 1)39 #expect(cart.total == 29.99)40 }41 42 @Test("Boş sepet kontrolü")43 func emptyCart() {44 #expect(cart.isEmpty)45 #expect(cart.total == 0)46 }47}Migration Stratejisi
Adım | Açıklama |
|---|---|
1 | Yeni testleri Swift Testing ile yaz |
2 | XCTest ve Swift Testing bir arada çalışabilir |
3 | Zamanla eski testleri migrate et |
4 | `XCTAssert*` → `#expect` / `#require` |
5 | `XCTestCase` → `@Suite struct` |
6 | `XCTestExpectation` → `confirmation` |
10. CI/CD Integration
bash
1# Tüm testleri çalıştır2swift test3 4# Tag ile filtreleme5swift test --filter ".tags.critical"6 7# Paralel çalıştırma (varsayılan)8swift test --parallel9 10# JUnit XML output (CI için)11swift test --xunit-output results.xml12 13# Code coverage14swift test --enable-code-coverage11. Best Practices
Test Yapısı (AAA Pattern)
swift
1@Test func orderCalculation() {2 // Arrange3 let order = Order()4 order.add(.init(name: "Widget", price: 10, quantity: 3))5 order.add(.init(name: "Gadget", price: 25, quantity: 1))6 7 // Act8 let total = order.calculateTotal(discount: 0.1)9 10 // Assert11 #expect(total == 49.50) // (30 + 25) * 0.912}Checklist
- Yeni testler Swift Testing ile yaz
- `@Test` macro'suna açıklayıcı display name ekle
- Parameterized test'ler ile test coverage artır
- Tags ile testleri organize et
- `#require` ile optional unwrap (crash yerine fail)
- Async testlerde `confirmation` kullan
- CI'da tag filtreleme: critical → her PR, slow → nightly
- Shared state varsa `.serialized` kullan
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:

