Tüm Yazılar
KategoriTesting
Okuma Süresi
19 dk okuma
Yayın Tarihi
...
Kelime Sayısı
1.880kelime

Kahveni hazırla - bu içerikli bir makale!

Swift Testing framework ile modern test yazımı. @Test macro, #expect, parameterized tests, tags ve XCTest'ten migration rehberi.

Swift Testing Framework: XCTest'in Geleceği

# 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


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 XCTest
3 
4class MathTests: XCTestCase {
5 func testAddition() {
6 XCTAssertEqual(2 + 2, 4)
7 }
8}
9 
10// Swift Testing (yeni)
11import Testing
12 
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 Testing
2 
3// Basit test
4@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 ile
11@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 test
18@Test(.disabled("API henüz hazır değil"))
19func apiIntegration() {
20 // Bu test çalıştırılmaz
21}
22 
23// Conditional test
24@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
25func onlyCITest() {
26 // Sadece CI ortamında çalışır
27}
28 
29// Bug referansı
30@Test(.bug("https://github.com/org/repo/issues/42", "Login timeout fix"))
31func loginTimeout() {
32 // Bug ile ilişkili test
33}
34 
35// Zaman limiti
36@Test(.timeLimit(.minutes(2)))
37func longRunningOperation() async {
38 await heavyComputation()
39}

3. #expect ve #require

swift
1import Testing
2 
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 check
12 let name: String? = "Ali"
13 #expect(name != nil)
14 
15 // Error bekleme
16 #expect(throws: ValidationError.invalidEmail) {
17 try validateEmail("invalid")
18 }
19 
20 // Herhangi bir error bekleme
21 #expect(throws: (any Error).self) {
22 try riskyOperation()
23 }
24 
25 // Error fırlatMAMASI bekleme
26 #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ışmaz
37 
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 test
3func validEmails(email: String) {
4 #expect(EmailValidator.isValid(email))
5}
6 
7// İki parametre
8@Test(arguments: [1, 2, 3], [10, 20, 30])
9func multiplication(a: Int, b: Int) {
10 #expect(a * b == a * b) // 9 kombinasyon test edilir
11}
12 
13// Zip ile eşleştirme (1:1)
14@Test(arguments: zip(
16 [true, false, false]
17))
18func emailValidation(email: String, expected: Bool) {
19 #expect(EmailValidator.isValid(email) == expected)
20}
21 
22// Enum case'leri
23enum 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 okuyabilir
29}
30 
31// Custom type
32struct LoginTestCase: CustomTestStringConvertible {
33 let email: String
34 let password: String
35 let shouldSucceed: Bool
36 
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 = mockRepo
10 }
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 suite
24 @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ımlama
2extension Tag {
3 @Tag static var critical: Self
4 @Tag static var slow: Self
5 @Tag static var network: Self
6 @Tag static var ui: Self
7 @Tag static var regression: Self
8}
9 
10// Tag kullanımı
11@Test(.tags(.critical, .network))
12func apiAuthentication() async {
13 // Kritik network testi
14}
15 
16@Test(.tags(.slow))
17func largeDataProcessing() {
18 // Yavaş test
19}
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 in
11 let observer = NotificationCenter.default.addObserver(
12 forName: .dataDidUpdate, object: nil, queue: .main
13 ) { _ in
14 confirm() // expectation.fulfill() yerine
15 }
16 
17 // Bildirimi tetikle
18 DataManager.shared.refresh()
19 
20 // cleanup
21 NotificationCenter.default.removeObserver(observer)
22 }
23}
24 
25// Birden fazla confirmation
26@Test func multipleEvents() async {
27 await confirmation("Event received", expectedCount: 3) { confirm in
28 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ışır
6 @Test func deleteRecord() { } // query'den sonra çalışır
7}
8 
9// Custom trait
10struct MemoryIntensive: TestTrait, SuiteTrait {
11 // Test veya suite'e metadata ekler
12}
13 
14extension TestTrait where Self == MemoryIntensive {
15 static var memoryIntensive: Self { MemoryIntensive() }
16}
17 
18@Test(.memoryIntensive)
19func processLargeFile() {
20 // Bellek yoğun test
21}

9. XCTest'ten Migration

swift
1// ÖNCE (XCTest)
2import XCTest
3 
4class CartTests: XCTestCase {
5 var cart: ShoppingCart!
6 
7 override func setUp() {
8 cart = ShoppingCart()
9 }
10 
11 override func tearDown() {
12 cart = nil
13 }
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 Testing
29 
30@Suite("Alışveriş Sepeti")
31struct CartTests {
32 let cart = ShoppingCart() // init = setUp
33 
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ır
2swift test
3 
4# Tag ile filtreleme
5swift test --filter ".tags.critical"
6 
7# Paralel çalıştırma (varsayılan)
8swift test --parallel
9 
10# JUnit XML output (CI için)
11swift test --xunit-output results.xml
12 
13# Code coverage
14swift test --enable-code-coverage

11. Best Practices

Test Yapısı (AAA Pattern)

swift
1@Test func orderCalculation() {
2 // Arrange
3 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 // Act
8 let total = order.calculateTotal(discount: 0.1)
9 
10 // Assert
11 #expect(total == 49.50) // (30 + 25) * 0.9
12}

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:

Etiketler

#Testing#Swift Testing#XCTest#TDD#CI/CD#Quality
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz