swift-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Swift Testing Framework

Swift Testing 框架

Modern testing with Swift Testing framework. No XCTest.
使用Swift Testing框架进行现代化测试,无需XCTest。

Overview

概述

Swift Testing replaces XCTest with a modern macro-based approach that's more concise, has better async support, and runs tests in parallel by default. The core principle: if you learned XCTest, unlearn it—Swift Testing works differently.
Swift Testing 采用基于宏的现代化方案替代XCTest,语法更简洁,异步支持更完善,且默认并行运行测试。核心原则:如果你学过XCTest,请先把它忘掉——Swift Testing的工作方式完全不同。

References

参考资料

Core Concepts

核心概念

Assertions

断言

MacroUse Case
#expect(expression)
Soft check — continues on failure. Use for most assertions.
#require(expression)
Hard check — stops test on failure. Use for preconditions only.
使用场景
#expect(expression)
软检查——失败后继续执行。适用于大多数断言场景。
#require(expression)
硬检查——失败后终止测试。仅适用于前置条件校验。

Optional Unwrapping

可选值解包

swift
let user = try #require(await fetchUser(id: "123"))
#expect(user.id == "123")
swift
let user = try #require(await fetchUser(id: "123"))
#expect(user.id == "123")

Test Structure

测试结构

swift
import Testing
@testable import YourModule

@Suite
struct FeatureTests {
    let sut: FeatureType
    
    init() throws {
        sut = FeatureType()
    }
    
    @Test("Description of behavior")
    func testBehavior() {
        #expect(sut.someProperty == expected)
    }
}
swift
import Testing
@testable import YourModule

@Suite
struct FeatureTests {
    let sut: FeatureType
    
    init() throws {
        sut = FeatureType()
    }
    
    @Test("Description of behavior")
    func testBehavior() {
        #expect(sut.someProperty == expected)
    }
}

Assertion Conversions

断言转换

XCTestSwift Testing
XCTAssert(expr)
#expect(expr)
XCTAssertEqual(a, b)
#expect(a == b)
XCTAssertNil(a)
#expect(a == nil)
XCTAssertNotNil(a)
#expect(a != nil)
try XCTUnwrap(a)
try #require(a)
XCTAssertThrowsError
#expect(throws: ErrorType.self) { }
XCTAssertNoThrow
#expect(throws: Never.self) { }
XCTestSwift Testing
XCTAssert(expr)
#expect(expr)
XCTAssertEqual(a, b)
#expect(a == b)
XCTAssertNil(a)
#expect(a == nil)
XCTAssertNotNil(a)
#expect(a != nil)
try XCTUnwrap(a)
try #require(a)
XCTAssertThrowsError
#expect(throws: ErrorType.self) { }
XCTAssertNoThrow
#expect(throws: Never.self) { }

Error Testing

错误测试

swift
#expect(throws: (any Error).self) { try riskyOperation() }
#expect(throws: NetworkError.self) { try fetch() }
#expect(throws: NetworkError.timeout) { try fetch() }
#expect(throws: Never.self) { try safeOperation() }
swift
#expect(throws: (any Error).self) { try riskyOperation() }
#expect(throws: NetworkError.self) { try fetch() }
#expect(throws: NetworkError.timeout) { try fetch() }
#expect(throws: Never.self) { try safeOperation() }

Parameterized Tests

参数化测试

swift
@Test("Validates inputs", arguments: zip(
    ["a", "b", "c"],
    [1, 2, 3]
))
func testInputs(input: String, expected: Int) {
    #expect(process(input) == expected)
}
Warning: Multiple collections WITHOUT zip creates Cartesian product.
swift
@Test("Validates inputs", arguments: zip(
    ["a", "b", "c"],
    [1, 2, 3]
))
func testInputs(input: String, expected: Int) {
    #expect(process(input) == expected)
}
注意: 不使用zip的多个集合会生成笛卡尔积。

Async Testing

异步测试

swift
@Test func testAsync() async throws {
    let result = try await fetchData()
    #expect(!result.isEmpty)
}
swift
@Test func testAsync() async throws {
    let result = try await fetchData()
    #expect(!result.isEmpty)
}

Confirmations

确认机制

swift
@Test func testCallback() async {
    await confirmation("callback received") { confirm in
        let sut = SomeType { confirm() }
        sut.triggerCallback()
    }
}
swift
@Test func testCallback() async {
    await confirmation("callback received") { confirm in
        let sut = SomeType { confirm() }
        sut.triggerCallback()
    }
}

Tags

标签

swift
extension Tag {
    @Tag static var fast: Self
    @Tag static var networking: Self
}

@Test(.tags(.fast, .networking))
func testNetworkCall() { }
swift
extension Tag {
    @Tag static var fast: Self
    @Tag static var networking: Self
}

@Test(.tags(.fast, .networking))
func testNetworkCall() { }

Common Pitfalls

常见陷阱

  1. Overusing
    #require
    — Use
    #expect
    for most checks
  2. Forgetting state isolation — Each test gets a NEW instance
  3. Accidental Cartesian product — Always use
    zip
    for paired inputs
  4. Not using
    .serialized
    — Apply for thread-unsafe legacy tests
  1. 过度使用
    #require
    — 大多数校验场景使用
    #expect
  2. 忘记状态隔离 — 每个测试都会获得一个新实例
  3. 意外生成笛卡尔积 — 成对输入时务必使用
    zip
  4. 未使用
    .serialized
    — 对线程不安全的遗留测试使用该属性

Common Mistakes

常见错误

  1. Overusing
    #require
    #require
    is for preconditions only. Using it for normal assertions means the test stops at first failure instead of reporting all failures. Use
    #expect
    for assertions,
    #require
    only when subsequent assertions depend on the value.
  2. Cartesian product bugs
    @Test(arguments: [a, b], [c, d])
    creates 4 combinations, not 2. Always use
    zip
    to pair arguments correctly:
    arguments: zip([a, b], [c, d])
    .
  3. Forgetting state isolation — Swift Testing creates a new test instance per test method. BUT shared state between tests (static variables, singletons) still leak. Use dependency injection or clean up singletons between tests.
  4. Parallel test conflicts — Swift Testing runs tests in parallel by default. Tests touching shared files, databases, or singletons will interfere. Use
    .serialized
    or isolation strategies.
  5. Not using
    async
    naturally
    — Wrapping async operations in
    Task { }
    defeats the purpose. Use
    async/await
    directly in test function signature:
    @Test func testAsync() async throws { }
    .
  6. Confirmation misuse
    confirmation
    is for verifying callbacks were called. Using it for assertions is wrong. Use
    #expect
    for assertions,
    confirmation
    for callback counts.
  1. 过度使用
    #require
    #require
    仅用于前置条件校验。在常规断言中使用它会导致测试在首次失败时终止,而非报告所有失败。断言使用
    #expect
    ,仅当后续断言依赖于当前值时才使用
    #require
  2. 笛卡尔积错误
    @Test(arguments: [a, b], [c, d])
    会生成4种组合,而非2种。务必使用
    zip
    来正确配对参数:
    arguments: zip([a, b], [c, d])
  3. 忘记状态隔离 — Swift Testing会为每个测试方法创建新的测试实例。但测试之间的共享状态(静态变量、单例)仍会泄漏。请使用依赖注入或在测试之间清理单例。
  4. 并行测试冲突 — Swift Testing默认并行运行测试。操作共享文件、数据库或单例的测试会互相干扰。请使用
    .serialized
    或采用隔离策略。
  5. 未自然使用
    async
    — 将异步操作包装在
    Task { }
    中会失去意义。直接在测试函数签名中使用
    async/await
    @Test func testAsync() async throws { }
  6. 确认机制误用
    confirmation
    用于验证回调是否被调用。将其用于断言是错误的。断言使用
    #expect
    confirmation
    用于校验回调次数。