用途
Swift Testing 是 Apple 在 WWDC 2024 推出的现代测试框架,用于编写 Swift 单元测试。它使用 Swift 宏(@Test、#expect)替代 XCTest 的命名约定,默认并行运行测试,并与 Swift 并发无缝集成。该技能面向需要编写单元测试、采用 Swift Testing 框架、让测试无需模拟器即可更快运行、为可测试性设计代码架构以及测试异步代码的场景。
核心原则:测试应当快速、可靠且富有表达力。最快的测试无需启动应用或模拟器。
速度层级
测试的运行速度因配置方式而差异巨大:
- swift test(Package):约 0.1 秒,适用于纯逻辑、模型、算法。
- Host Application: None:约 3 秒,适用于框架代码、无 UI 依赖。
- Bypass app launch:约 6 秒,适用于应用目标但跳过初始化。
- Full app launch:20-60 秒,适用于 UI 测试、集成测试。
关键洞察:将可测试逻辑移入 Swift Package 或框架,然后用 swift test 或 "None" 宿主应用进行测试。
构建块
@Test 函数
使用 import Testing 后,用 @Test 标注函数。与 XCTest 的关键区别:无需 test 前缀,@Test 属性是显式的;可以是全局函数,而不只是类中的方法;支持 async、throws 和 actor 隔离;每个测试在包含它的套件的新实例上运行。
#expect 与 #require
#expect 为基本期望,失败后测试继续;#require 为必需期望,失败后测试停止,也可用于安全解包可选值。相比 XCTAssert 的优势:自动捕获源代码和子值;单个宏处理所有运算符(==、>、contains 等);无需专用断言(XCTAssertEqual、XCTAssertNil 等)。
错误测试
可用 #expect(throws:) 期望任意错误、特定错误类型或特定错误值,也支持自定义验证闭包。
@Suite 类型
用 @Suite 组织测试。推荐使用结构体(值语义,避免意外共享状态);每个 @Test 获得自己的套件实例;使用 init 做设置,deinit 做清理(仅 actor/类);支持嵌套套件。
Traits
Traits 用于自定义测试行为,包括显示名称、带原因禁用、条件执行、时间限制、Bug 引用、OS 版本要求等。
标签组织
通过扩展 Tag 定义标签,应用于测试或整个套件。用途:运行测试子集、从快速反馈循环中排除慢测试、跨文件/套件分组相关测试。
参数化测试
将重复测试转换为单个参数化测试,支持双集合参数化(全组合或 zip 配对)。相比 for 循环的优势:所有参数都会运行而非首个失败即停;每个参数单独显示;并行执行;可单独重跑某个参数。
快速测试:面向可测试性的架构
策略 1:用 Swift Package 承载逻辑(最快)
将应用逻辑抽取为 Swift Package,测试用 swift test(约 0.4 秒)而非 xcodebuild test(约 25 秒),无需模拟器、无需启动应用。步骤包括:创建 Package.swift;创建包含应用项目和包的 .xcworkspace 并链接;移动逻辑并暴露根视图(应用使用的类型需为 public,创建接受依赖注入的 public 根视图);将 App.swift 变为薄壳。
哪些留在应用目标、哪些移入包:@main App.swift(薄壳)、资源目录、Info.plist、entitlements、启动屏留在应用目标;模型、视图模型、服务、业务逻辑、算法、导航、状态管理、工具与扩展移入包。测试使用 @testable import 访问内部内容。
运行测试:cd MyAppCore 后 swift test 运行全部测试,或用 --filter 运行单个套件。
现有项目的渐进式抽取
阶段 1:先抽取叶子模块(数据模型与 DTO、网络层、业务逻辑与校验规则、工具扩展)。阶段 2:打破循环依赖(在包中定义协议,由应用目标注入实现,待依赖齐备后移入包)。阶段 3:同时维护两个测试目标,逐步迁移。目标:每次抽取都应保持应用可构建、所有测试通过,一次只抽取一个模块边界。
策略 2:无宿主应用的框架
对于必须留在应用项目中的代码:创建框架目标、移入模型代码、将需要外部访问的类型设为 public、在使用处添加 import、在测试目标设置中将 Host Application 设为 "None"。构建+测试时间约 3 秒,而非启动应用的 20-60 秒。
策略 3:绕过 SwiftUI 应用启动
若无法使用框架,可绕过应用启动:简单方案是在 WindowGroup 中判断是否在运行测试;彻底方案是自定义启动代码,测试时运行空应用。
异步测试
支持基本异步测试、用 continuations 测试回调、用 confirmations 确认多个事件(也可确认某事从未发生)。
用并发扩展实现可靠的异步测试
问题:异步测试可能因调度不可预测而不稳定。解决方案:使用 Point-Free 的 swift-concurrency-extras 中的 withMainSerialExecutor,将异步工作序列化到主线程,使挂起点确定。
用 TestClock 实现确定性时间
使用 Point-Free 的 swift-clocks 在测试中控制时间。时钟类型:TestClock(手动推进时间,确定性)、ImmediateClock(所有 sleep 立即返回,适合预览)、UnimplementedClock(被使用时失败,用于捕获意外的时间依赖)。
并行测试
Swift Testing 默认并行运行测试。当测试共享外部状态时,可用 @Suite(.serialized) 序列化套件,或对参数化测试用例使用 .serialized。隐藏依赖(如测试依赖执行顺序)应修复而非序列化,随机顺序有助于暴露这类 Bug。
已知问题
用 withKnownIssue 处理预期失败而不产生噪音,支持条件性已知问题。相比 .disabled 的优势:测试仍会编译(捕获语法错误);问题修复时会收到通知;结果显示为 "expected failure" 而非 "skipped"。
从 XCTest 迁移
对照关系:func testFoo() 对应 @Test func foo();XCTAssertEqual(a, b) 对应 #expect(a == b);XCTAssertNil(x) 对应 #expect(x == nil);XCTAssertThrowsError 对应 #expect(throws:);XCTUnwrap(x) 对应 try #require(x);class FooTests: XCTestCase 对应 @Suite struct FooTests;setUp()/tearDown() 对应 init/deinit;continueAfterFailure = false 对应 #require(逐期望);addTeardownBlock 对应 deinit 或 defer。
仍需使用 XCTest 的场景:UI 测试(XCUIApplication)、性能测试(XCTMetric)、Objective-C 测试。
迁移建议:两个框架可在同一目标中共存;逐文件增量迁移;将相似的 XCTest 合并为参数化 Swift 测试;单测试的 XCTestCase 转为全局 @Test 函数。
限制
资料未提供该技能的收费、价格、平台支持范围等具体信息。