关于 swift-testing
面向 Swift 开发者的 Swift Testing 框架指南,适用于替换 XCTest。内容涵盖宏断言、异步与参数化测试,以及迁移时的常见问题。
主要功能
- 01基于宏的 #expect 与 #require 断言
- 02异步测试与回调确认
- 03通过参数配对构建参数化测试
- 04测试标签与并行执行注意事项
- 05XCTest 断言迁移方式
适用场景
- 01将 Swift 测试套件中的 XCTest 断言替换为新写法
- 02测试异步函数和回调行为
- 03为参数化测试创建配对输入
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ johnrogers/ claude-swift-engineering --skill swift-testing安装后在 Claude Code 中输入/swift-testing确认已加载。
详细介绍
Swift Testing 测试框架
使用 Swift Testing 框架进行现代化测试。不使用 XCTest。
概述
Swift Testing 以基于宏的现代化方式取代 XCTest,代码更简洁,对异步操作的支持更好,并且默认并行运行测试。其核心原则是:如果你学过 XCTest,就把它忘掉——Swift Testing 的工作方式不同。
参考资料
核心概念
断言
| 宏 | 用途 |
|---|---|
#expect(expression) | 软检查——失败后继续运行。大多数断言都应使用它。 |
#require(expression) | 硬检查——失败时停止测试。仅用于前置条件。 |
可选值解包
let user = try #require(await fetchUser(id: "123"))
#expect(user.id == "123")
测试结构
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)
}
}
断言转换
| XCTest | Swift 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) { } |
错误测试
#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() }
参数化测试
@Test("Validates inputs", arguments: zip(
["a", "b", "c"],
[1, 2, 3]
))
func testInputs(input: String, expected: Int) {
#expect(process(input) == expected)
}
**警告:**多个集合不使用 zip 会产生笛卡尔积。
异步测试
@Test func testAsync() async throws {
let result = try await fetchData()
#expect(!result.isEmpty)
}
确认
@Test func testCallback() async {
await confirmation("callback received") { confirm in
let sut = SomeType { confirm() }
sut.triggerCallback()
}
}
标签
extension Tag {
@Tag static var fast: Self
@Tag static var networking: Self
}
@Test(.tags(.fast, .networking))
func testNetworkCall() { }
常见陷阱
- 过度使用
#require——大多数检查应使用#expect - 忘记状态隔离——每个测试都会获得一个全新的实例
- 意外产生笛卡尔积——成对输入始终使用
zip - 未使用
.serialized——用于线程不安全的旧版测试
常见错误
-
过度使用
#require——#require仅用于前置条件。将它用于常规断言会导致测试在首次失败时停止,而不是报告所有失败。断言应使用#expect;只有当后续断言依赖该值时,才使用#require。 -
笛卡尔积错误——
@Test(arguments: [a, b], [c, d])会产生 4 种组合,而不是 2 种。始终使用zip正确配对参数:arguments: zip([a, b], [c, d])。 -
忘记状态隔离——Swift Testing 会为每个测试方法创建一个新的测试实例。但测试之间共享的状态(静态变量、单例)仍会泄漏。使用依赖注入,或在测试之间清理单例。
-
并行测试冲突——Swift Testing 默认并行运行测试。访问共享文件、数据库或单例的测试会相互干扰。使用
.serialized或隔离策略。 -
未自然地使用
async——将异步操作包装在Task { }中会失去其意义。直接在测试函数签名中使用async/await:@Test func testAsync() async throws { }。 -
误用确认机制——
confirmation用于验证是否调用了回调。将其用于断言是不正确的。断言使用#expect,回调次数使用confirmation。