Section 11/171 menit

11. Best Practice: Testing Code Konkuren

11. Best Practice: Testing Code Konkuren

BP 11.1: Test Actor secara Functional

Test method actor lewat public API, bukan internal state.

swift
@Test
func incrementWorks() async {
    let counter = Counter()
    await counter.increment()
    let value = await counter.value
    #expect(value == 1)
}

BP 11.2: Stress Test untuk Race Detection

Untuk verifikasi safety di bawah load:

swift
@Test
func incrementsAreAtomic() async {
    let c = Counter()
    await withTaskGroup(of: Void.self) { group in
        for _ in 0..<10_000 {
            group.addTask { await c.increment() }
        }
    }
    #expect(await c.value == 10_000)
}

BP 11.3: Mock Clock untuk Deterministic Timing

Jangan pakai Task.sleep di test produksi. Pakai abstraksi Clock.

swift
protocol AppClock: Sendable {
    func sleep(for: Duration) async throws
}

struct SystemClock: AppClock {
    func sleep(for d: Duration) async throws {
        try await Task.sleep(for: d)
    }
}

// Test pakai mock yang resolve instan
struct InstantClock: AppClock {
    func sleep(for: Duration) async throws { }
}

BP 11.4: Hindari Task.sleep di Test sebagai "Tunggu"

Anti-pattern paling umum: try await Task.sleep(for: .seconds(1)) untuk "tunggu async selesai". Itu flaky.

swift
// ❌ Flaky
@Test
func updatesViewModel() async {
    let vm = ViewModel()
    Task { await vm.load() }
    try await Task.sleep(for: .seconds(1))  // tebak-tebakan
    #expect(vm.isLoaded)
}

// ✓ Await langsung
@Test
func updatesViewModel() async {
    let vm = ViewModel()
    await vm.load()
    #expect(vm.isLoaded)
}

BP 11.5: Test MainActor View dengan @MainActor Suite

Untuk test ViewModel @MainActor, tandai test suite @MainActor juga.

swift
@Suite("HomeViewModel", .serialized)
@MainActor
struct HomeViewModelTests {
    @Test
    func loadShowsLoadingThenItems() async {
        let vm = HomeViewModel(service: MockService())
        #expect(vm.items.isEmpty)
        await vm.load()
        #expect(vm.items.count == 3)
    }
}