testing-swift

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Swift Testing Framework

Swift Testing 框架

Modern testing with
@Test
,
#expect
, and
@Suite
. Replaces XCTest for new projects.
借助
@Test
#expect
@Suite
实现现代化测试。可在新项目中替代XCTest。

Critical Constraints

关键约束

  • ❌ DO NOT use
    XCTAssertEqual
    in Swift Testing → ✅ Use
    #expect(a == b)
  • ❌ DO NOT use
    func testSomething()
    naming convention → ✅ Use
    @Test func something()
  • ❌ DO NOT subclass
    XCTestCase
    → ✅ Use
    @Suite struct
    or plain
    @Test
    functions
  • ❌ DO NOT use
    XCTAssertThrowsError
    → ✅ Use
    #expect(throws:) { try something() }
  • ❌ DO NOT use
    setUp/tearDown
    → ✅ Use
    init/deinit
    on
    @Suite
    struct or actor
  • ❌ 请勿在Swift Testing中使用
    XCTAssertEqual
    → ✅ 请使用
    #expect(a == b)
  • ❌ 请勿使用
    func testSomething()
    命名规范 → ✅ 请使用
    @Test func something()
  • ❌ 请勿继承
    XCTestCase
    → ✅ 请使用
    @Suite struct
    或直接使用
    @Test
    函数
  • ❌ 请勿使用
    XCTAssertThrowsError
    → ✅ 请使用
    #expect(throws:) { try something() }
  • ❌ 请勿使用
    setUp/tearDown
    → ✅ 请在
    @Suite
    结构体或actor中使用
    init/deinit

Basic Test

基础测试

swift
import Testing

@Test("Adding items increases count")
func addItem() {
    var list = ShoppingList()
    list.add("Milk")
    #expect(list.items.count == 1)
    #expect(list.items.first == "Milk")
}
swift
import Testing

@Test("Adding items increases count")
func addItem() {
    var list = ShoppingList()
    list.add("Milk")
    #expect(list.items.count == 1)
    #expect(list.items.first == "Milk")
}

@Suite for Organization

使用@Suite进行测试组织

swift
@Suite("Shopping List Tests")
struct ShoppingListTests {
    var list: ShoppingList

    init() {
        list = ShoppingList()  // Replaces setUp()
    }

    @Test func addItem() {
        list.add("Bread")
        #expect(list.items.contains("Bread"))
    }

    @Test func removeItem() {
        list.add("Bread")
        list.remove("Bread")
        #expect(list.items.isEmpty)
    }
}
swift
@Suite("Shopping List Tests")
struct ShoppingListTests {
    var list: ShoppingList

    init() {
        list = ShoppingList()  // 替代setUp()
    }

    @Test func addItem() {
        list.add("Bread")
        #expect(list.items.contains("Bread"))
    }

    @Test func removeItem() {
        list.add("Bread")
        list.remove("Bread")
        #expect(list.items.isEmpty)
    }
}

Parameterized Tests

参数化测试

swift
@Test("Validates email format", arguments: [
    ("user@example.com", true),
    ("invalid", false),
    ("a@b.c", true),
    ("@missing.com", false),
])
func emailValidation(email: String, isValid: Bool) {
    #expect(EmailValidator.isValid(email) == isValid)
}

// With enums
enum Priority: CaseIterable { case low, medium, high }

@Test("All priorities have colors", arguments: Priority.allCases)
func priorityColor(priority: Priority) {
    #expect(priority.color != nil)
}
swift
@Test("Validates email format", arguments: [
    ("user@example.com", true),
    ("invalid", false),
    ("a@b.c", true),
    ("@missing.com", false),
])
func emailValidation(email: String, isValid: Bool) {
    #expect(EmailValidator.isValid(email) == isValid)
}

// 使用枚举
enum Priority: CaseIterable { case low, medium, high }

@Test("All priorities have colors", arguments: Priority.allCases)
func priorityColor(priority: Priority) {
    #expect(priority.color != nil)
}

Expectations

断言(Expectations)

swift
// Equality
#expect(result == expected)

// Boolean
#expect(user.isActive)
#expect(!list.isEmpty)

// Optional
#expect(user.name != nil)
let name = try #require(user.name)  // Unwrap or fail

// Throws
#expect(throws: ValidationError.self) {
    try validator.validate(invalidInput)
}

// Specific error
#expect {
    try parser.parse("")
} throws: { error in
    guard let parseError = error as? ParseError else { return false }
    return parseError.code == .emptyInput
}

// No throw
#expect(throws: Never.self) {
    try safeOperation()
}
swift
// 相等断言
#expect(result == expected)

// 布尔断言
#expect(user.isActive)
#expect(!list.isEmpty)

// 可选类型断言
#expect(user.name != nil)
let name = try #require(user.name)  // 解包失败则测试终止

// 异常断言
#expect(throws: ValidationError.self) {
    try validator.validate(invalidInput)
}

// 指定类型异常断言
#expect {
    try parser.parse("")
} throws: { error in
    guard let parseError = error as? ParseError else { return false }
    return parseError.code == .emptyInput
}

// 无异常断言
#expect(throws: Never.self) {
    try safeOperation()
}

Testing Async Code

异步代码测试

swift
@Test func asyncFetch() async throws {
    let service = DataService()
    let items = try await service.fetchItems()
    #expect(!items.isEmpty)
}

@Test(.timeLimit(.minutes(1)))
func longRunningOperation() async throws {
    let result = try await processor.processLargeFile()
    #expect(result.isComplete)
}
swift
@Test func asyncFetch() async throws {
    let service = DataService()
    let items = try await service.fetchItems()
    #expect(!items.isEmpty)
}

@Test(.timeLimit(.minutes(1)))
func longRunningOperation() async throws {
    let result = try await processor.processLargeFile()
    #expect(result.isComplete)
}

Testing @Observable Models

@Observable 模型测试

swift
import Testing
import Observation

@Test func modelUpdates() {
    let model = CounterModel()
    #expect(model.count == 0)

    model.increment()
    #expect(model.count == 1)

    model.reset()
    #expect(model.count == 0)
}

@Test func asyncModelLoading() async {
    let model = ItemListModel()
    await model.loadItems()
    #expect(!model.items.isEmpty)
    #expect(!model.isLoading)
}
swift
import Testing
import Observation

@Test func modelUpdates() {
    let model = CounterModel()
    #expect(model.count == 0)

    model.increment()
    #expect(model.count == 1)

    model.reset()
    #expect(model.count == 0)
}

@Test func asyncModelLoading() async {
    let model = ItemListModel()
    await model.loadItems()
    #expect(!model.items.isEmpty)
    #expect(!model.isLoading)
}

Confirmation (for Events/Callbacks)

确认机制(用于事件/回调)

swift
@Test func notificationFires() async {
    await confirmation("Callback received") { confirm in
        let observer = EventObserver { event in
            #expect(event.type == .update)
            confirm()
        }
        observer.startListening()
        EventEmitter.emit(.update)
    }
}

// Expected count
await confirmation("Multiple events", expectedCount: 3) { confirm in
    for _ in 0..<3 {
        EventEmitter.emit(.tick)
        confirm()
    }
}
swift
@Test func notificationFires() async {
    await confirmation("Callback received") { confirm in
        let observer = EventObserver { event in
            #expect(event.type == .update)
            confirm()
        }
        observer.startListening()
        EventEmitter.emit(.update)
    }
}

// 指定确认次数
await confirmation("Multiple events", expectedCount: 3) { confirm in
    for _ in 0..<3 {
        EventEmitter.emit(.tick)
        confirm()
    }
}

Test Traits

测试特性(Test Traits)

swift
@Test(.disabled("Flaky on CI"))
func unreliableTest() { }

@Test(.bug("https://github.com/org/repo/issues/42", "Crashes on empty input"))
func knownBug() { }

@Test(.timeLimit(.seconds(5)))
func mustBeFast() async { }

@Test(.tags(.performance))
func benchmarkSort() { }

extension Tag {
    @Tag static var performance: Self
    @Tag static var integration: Self
}
swift
@Test(.disabled("Flaky on CI"))
func unreliableTest() { }

@Test(.bug("https://github.com/org/repo/issues/42", "Crashes on empty input"))
func knownBug() { }

@Test(.timeLimit(.seconds(5)))
func mustBeFast() async { }

@Test(.tags(.performance))
func benchmarkSort() { }

extension Tag {
    @Tag static var performance: Self
    @Tag static var integration: Self
}

Migration from XCTest

从XCTest迁移

XCTestSwift Testing
class FooTests: XCTestCase
@Suite struct FooTests
func testBar()
@Test func bar()
XCTAssertEqual(a, b)
#expect(a == b)
XCTAssertTrue(x)
#expect(x)
XCTAssertNil(x)
#expect(x == nil)
XCTUnwrap(x)
try #require(x)
XCTAssertThrowsError
#expect(throws:) { }
setUp()
init()
tearDown()
deinit
XCTSkip
try #require(condition)
XCTestSwift Testing
class FooTests: XCTestCase
@Suite struct FooTests
func testBar()
@Test func bar()
XCTAssertEqual(a, b)
#expect(a == b)
XCTAssertTrue(x)
#expect(x)
XCTAssertNil(x)
#expect(x == nil)
XCTUnwrap(x)
try #require(x)
XCTAssertThrowsError
#expect(throws:) { }
setUp()
init()
tearDown()
deinit
XCTSkip
try #require(condition)

References

参考资料