Section 4/161 menit

4. Custom Traits

4. Custom Traits

Traits memungkinkan Anda menambahkan metadata, mengubah perilaku test, atau skip test berdasarkan kondisi kustom.

Membuat TestTrait Kustom

swift
import Testing

// Trait yang menandai test sebagai "slow" — butuh lebih dari 1 detik
struct SlowTestTrait: TestTrait, SuiteTrait {
    let estimatedDuration: Duration
    var isRecursive: Bool { true }  // Berlaku ke nested suites juga

    func prepare(for test: Test) async throws {
        // Bisa melakukan setup khusus sebelum slow test
        // Misalnya: pastikan tidak jalan di CI yang cepat jika tidak diperlukan
        if ProcessInfo.processInfo.environment["CI_FAST_ONLY"] != nil {
            throw XCTSkip("Slow test dilewati di fast CI environment")
        }
    }
}

extension Trait where Self == SlowTestTrait {
    static func slow(estimated duration: Duration = .seconds(5)) -> SlowTestTrait {
        SlowTestTrait(estimatedDuration: duration)
    }
}

// Penggunaan
@Test(.slow(estimated: .seconds(10)))
func testDatabaseMigrationPerformance() async throws {
    // Test yang memang butuh waktu lama
    let result = try await performFullMigration()
    #expect(result.success)
}

Trait untuk Environment Condition

swift
// Trait yang skip test jika environment tertentu tidak ada
struct RequiresEnvironmentTrait: TestTrait {
    let variableName: String
    let description: String

    func prepare(for test: Test) async throws {
        guard ProcessInfo.processInfo.environment[variableName] != nil else {
            throw XCTSkip("\(description) tidak tersedia di environment ini")
        }
    }
}

extension Trait where Self == RequiresEnvironmentTrait {
    static func requiresEnvironment(_ variable: String, description: String) -> RequiresEnvironmentTrait {
        RequiresEnvironmentTrait(variableName: variable, description: description)
    }
}

// Test yang butuh API key di environment
@Test(.requiresEnvironment("OPENAI_API_KEY", description: "OpenAI API Key"))
func testExternalAPIIntegration() async throws {
    let client = ExternalAPIClient()
    let result = try await client.fetchData()
    #expect(!result.isEmpty)
}

Trait untuk Setup dan Teardown Kustom

swift
// Trait yang otomatis cleanup file temporary setelah test
struct TemporaryDirectoryTrait: TestTrait {
    var tempDirectoryURL: URL?

    mutating func prepare(for test: Test) async throws {
        let tempDir = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString)
        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
        tempDirectoryURL = tempDir
    }

    // Teardown — dipanggil setelah test selesai
    func tearDown(for test: Test) async throws {
        if let url = tempDirectoryURL {
            try? FileManager.default.removeItem(at: url)
        }
    }
}

// Trait gabungan untuk test yang butuh network + retry
struct NetworkIntegrationTrait: TestTrait {
    let maxRetries: Int

    func prepare(for test: Test) async throws {
        var lastError: Error?
        for attempt in 1...maxRetries {
            do {
                try await checkNetworkConnectivity()
                return  // Berhasil
            } catch {
                lastError = error
                if attempt < maxRetries {
                    try await Task.sleep(nanoseconds: UInt64(attempt) * 1_000_000_000)
                }
            }
        }
        throw lastError ?? NetworkError.unavailable
    }
}