swift-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwift 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
断言
| Macro | Use Case |
|---|---|
| Soft check — continues on failure. Use for most assertions. |
| Hard check — stops test on failure. Use for preconditions only. |
| 宏 | 使用场景 |
|---|---|
| 软检查——失败后继续执行。适用于大多数断言场景。 |
| 硬检查——失败后终止测试。仅适用于前置条件校验。 |
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
断言转换
| XCTest | Swift Testing |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| XCTest | Swift Testing |
|---|---|
| |
| |
| |
| |
| |
| |
| |
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
常见陷阱
- Overusing — Use
#requirefor most checks#expect - Forgetting state isolation — Each test gets a NEW instance
- Accidental Cartesian product — Always use for paired inputs
zip - Not using — Apply for thread-unsafe legacy tests
.serialized
- 过度使用— 大多数校验场景使用
#require#expect - 忘记状态隔离 — 每个测试都会获得一个新实例
- 意外生成笛卡尔积 — 成对输入时务必使用
zip - 未使用— 对线程不安全的遗留测试使用该属性
.serialized
Common Mistakes
常见错误
-
Overusing—
#requireis for preconditions only. Using it for normal assertions means the test stops at first failure instead of reporting all failures. Use#requirefor assertions,#expectonly when subsequent assertions depend on the value.#require -
Cartesian product bugs —creates 4 combinations, not 2. Always use
@Test(arguments: [a, b], [c, d])to pair arguments correctly:zip.arguments: zip([a, b], [c, d]) -
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.
-
Parallel test conflicts — Swift Testing runs tests in parallel by default. Tests touching shared files, databases, or singletons will interfere. Useor isolation strategies.
.serialized -
Not usingnaturally — Wrapping async operations in
asyncdefeats the purpose. UseTask { }directly in test function signature:async/await.@Test func testAsync() async throws { } -
Confirmation misuse —is for verifying callbacks were called. Using it for assertions is wrong. Use
confirmationfor assertions,#expectfor callback counts.confirmation
-
过度使用—
#require仅用于前置条件校验。在常规断言中使用它会导致测试在首次失败时终止,而非报告所有失败。断言使用#require,仅当后续断言依赖于当前值时才使用#expect。#require -
笛卡尔积错误 —会生成4种组合,而非2种。务必使用
@Test(arguments: [a, b], [c, d])来正确配对参数:zip。arguments: zip([a, b], [c, d]) -
忘记状态隔离 — Swift Testing会为每个测试方法创建新的测试实例。但测试之间的共享状态(静态变量、单例)仍会泄漏。请使用依赖注入或在测试之间清理单例。
-
并行测试冲突 — Swift Testing默认并行运行测试。操作共享文件、数据库或单例的测试会互相干扰。请使用或采用隔离策略。
.serialized -
未自然使用— 将异步操作包装在
async中会失去意义。直接在测试函数签名中使用Task { }:async/await。@Test func testAsync() async throws { } -
确认机制误用 —用于验证回调是否被调用。将其用于断言是错误的。断言使用
confirmation,#expect用于校验回调次数。confirmation