关于 swift-testing
面向 Swift 开发者的 Swift Testing 测试指导技能,涵盖测试组织、测试替身、固定数据、集成与快照测试,以及从 XCTest 迁移。
主要功能
- 01Swift Testing 语法与组织
- 02异步和参数化测试模式
- 03Mock、Stub、Spy 与固定数据
- 04集成测试与快照测试
- 05XCTest 迁移指导
适用场景
- 01新建 Swift Testing 测试套件
- 02测试异步代码和多组输入
- 03选择测试替身与测试数据
- 04迁移现有 XCTest 测试
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ bocato/ swift-testing-agent-skill --skill swift-testing安装后在 Claude Code 中输入/swift-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/_index.mdasync-testing.mddump-snapshot-testing.mdfixtures.mdintegration-testing.mdmigration-xctest.mdparameterized-tests.mdsnapshot-testing.mdtest-doubles.mdtest-organization.md详细介绍
Swift Testing
概述
此技能提供 Swift Testing 专业指南,涵盖现代 Swift Testing 框架、测试替身(模拟对象、桩对象、间谍对象)、测试固件、集成测试、快照测试,以及从 XCTest 迁移。使用此技能帮助开发者遵循 F.I.R.S.T. 原则和 Arrange-Act-Assert 模式,编写可靠且易于维护的测试。
Agent 行为约定(遵循以下规则)
- 所有新测试都使用 Swift Testing 框架(
@Test、#expect、#require、@Suite),不要使用 XCTest。 - 始终将测试划分为清晰的 Arrange-Act-Assert 阶段。
- 遵循 F.I.R.S.T. 原则:快速(Fast)、隔离(Isolated)、可重复(Repeatable)、自验证(Self-Validating)、及时(Timely)。
- 根据 Martin Fowler 的分类体系使用恰当的测试替身术语(Dummy、Fake、Stub、Spy、SpyingStub、Mock)。
- 将测试固件放在靠近模型的位置,并使用
#if DEBUG,不要放在测试目标中。 - 将测试替身放在靠近接口的位置,并使用
#if DEBUG,不要放在测试目标中。 - 优先采用状态验证,而非行为验证——测试更简单,也更不易脆弱。
- 软断言(失败后继续执行)使用
#expect,硬断言(失败后停止执行)使用#require。
快速决策树
开发者需要测试指导时,请遵循此决策树:
-
刚开始使用 Swift Testing?
- 阅读
references/test-organization.md,了解测试套件、标签和特征 - 阅读
references/async-testing.md,了解异步测试模式
- 阅读
-
需要创建测试数据?
- 阅读
references/fixtures.md,了解测试固件模式及其放置位置 - 阅读
references/test-doubles.md,了解 Mock/Stub/Spy 模式
- 阅读
-
需要测试多个输入?
- 阅读
references/parameterized-tests.md,了解参数化测试
- 阅读
-
需要测试模块间的交互?
- 阅读
references/integration-testing.md,了解集成测试模式
- 阅读
-
需要测试 UI 是否出现回归?
- 阅读
references/snapshot-testing.md,了解快照测试设置
- 阅读
-
需要测试数据结构或状态?
- 阅读
references/dump-snapshot-testing.md,了解基于文本的快照测试
- 阅读
-
需要从 XCTest 迁移?
- 阅读
references/migration-xctest.md,了解迁移指南
- 阅读
优先分诊手册(常见错误 -> 下一步最佳操作)
- “XCTAssertEqual 不可用” / 需要让测试现代化
- 使用
references/migration-xctest.md将 XCTest 迁移到 Swift Testing
- 使用
- 需要测试异步代码
- 使用
references/async-testing.md了解异步模式、确认机制和超时
- 使用
- 测试运行缓慢或不稳定
- 检查 F.I.R.S.T. 原则,并根据
references/test-doubles.md使用恰当的模拟
- 检查 F.I.R.S.T. 原则,并根据
- 需要确定性测试数据
- 使用
references/fixtures.md了解使用固定日期的测试固件模式
- 使用
- 需要高效测试多个场景
- 使用
references/parameterized-tests.md进行参数化测试
- 使用
- 需要验证组件间的交互
- 使用
references/integration-testing.md了解集成测试模式
- 使用
核心语法
基本测试
import Testing
@Test func basicTest() {
#expect(1 + 1 == 2)
}
带描述的测试
@Test("Adding items increases cart count")
func addItem() {
let cart = Cart()
cart.add(item)
#expect(cart.count == 1)
}
异步测试
@Test func asyncOperation() async throws {
let result = try await service.fetch()
#expect(result.isValid)
}
Arrange-Act-Assert 模式
将每个测试划分为清晰的阶段:
@Test func calculateTotal() {
// Given
let cart = ShoppingCart()
cart.add(Item(price: 10))
cart.add(Item(price: 20))
// When
let total = cart.calculateTotal()
// Then
#expect(total == 30)
}
断言
#expect - 软断言
失败后继续执行测试:
@Test func multipleExpectations() {
let user = User(name: "Alice", age: 30)
#expect(user.name == "Alice") // If fails, test continues
#expect(user.age == 30) // This still runs
}
#require - 硬断言
失败后停止执行测试:
@Test func requireExample() throws {
let user = try #require(fetchUser()) // Stops if nil
#expect(user.name == "Alice")
}
错误测试
@Test func throwsError() {
#expect(throws: ValidationError.self) {
try validate(invalidInput)
}
}
@Test func throwsSpecificError() {
#expect(throws: ValidationError.emptyField) {
try validate("")
}
}
F.I.R.S.T. 原则
| 原则 | 描述 | 应用 |
|---|---|---|
| 快速(Fast) | 测试在毫秒内执行 | 模拟开销大的操作 |
| 隔离(Isolated) | 测试之间互不依赖 | 每项测试使用全新实例 |
| 可重复(Repeatable) | 每次结果都相同 | 模拟日期、网络和外部依赖 |
| 自验证(Self-Validating) | 自动报告通过/失败 | 使用 #expect,绝不依赖 print() |
| 及时(Timely) | 编写代码的同时编写测试 | 使用参数化测试覆盖边界情况 |
测试替身速查
| 类型 | 用途 | 验证方式 |
|---|---|---|
| Dummy | 填充参数,从不使用 | 不适用 |
| Fake | 带有简化实现的可用实现 | 状态 |
| Stub | 提供预设答案 | 状态 |
| Spy | 记录调用以供验证 | 状态 |
| SpyingStub | Stub + Spy 的组合(最常见) | 状态 |
| Mock | 预先编程的期望值,可自行验证 | 行为 |
重要提示:Swift 社区所说的 “Mock” 通常是 SpyingStub。
详细模式请参阅 references/test-doubles.md。
测试替身的放置位置
将测试替身放在靠近接口的位置,而不是测试目标中:
// In PersonalRecordsCore-Interface/Sources/...
public protocol PersonalRecordsRepositoryProtocol: Sendable {
func getAll() async throws -> [PersonalRecord]
func save(_ record: PersonalRecord) async throws
}
#if DEBUG
public final class PersonalRecordsRepositorySpyingStub: PersonalRecordsRepositoryProtocol {
// Spy: Captured calls
public private(set) var savedRecords: [PersonalRecord] = []
// Stub: Configurable responses
public var recordsToReturn: [PersonalRecord] = []
public var errorToThrow: Error?
public func getAll() async throws -> [PersonalRecord] {
if let error = errorToThrow { throw error }
return recordsToReturn
}
public func save(_ record: PersonalRecord) async throws {
if let error = errorToThrow { throw error }
savedRecords.append(record)
}
}
#endif
夹具
将夹具放在靠近模型的位置:
// In Sources/Models/PersonalRecord.swift
public struct PersonalRecord: Equatable, Sendable {
public let id: UUID
public let weight: Double
// ...
}
#if DEBUG
extension PersonalRecord {
public static func fixture(
id: UUID = UUID(),
weight: Double = 100.0
// ... defaults for all properties
) -> PersonalRecord {
PersonalRecord(id: id, weight: weight)
}
}
#endif
详细模式请参阅 references/fixtures.md。
测试金字塔
+-------------+
| UI Tests | 5% - End-to-end flows
| (E2E) |
+-------------+
| Integration | 15% - Module interactions
| Tests |
+-------------+
| Unit | 80% - Individual components
| Tests |
+-------------+
参考文件
根据需要加载以下文件,了解特定主题:
test-organization.md- 测试套件、标签、特征和并行执行parameterized-tests.md- 高效测试多个输入async-testing.md- 异步模式、确认、超时和取消migration-xctest.md- 从 XCTest 迁移到 Swift Testing 的完整指南test-doubles.md- 含示例的完整分类(Dummy、Fake、Stub、Spy、SpyingStub、Mock)fixtures.md- 夹具模式、放置位置和最佳实践integration-testing.md- 模块交互测试模式snapshot-testing.md- 使用 SnapshotTesting 库进行 UI 回归测试dump-snapshot-testing.md- 对数据结构进行基于文本的快照测试
最佳实践总结
- 新测试使用 Swift Testing - 语法现代,功能更完善
- 遵循 Arrange-Act-Assert - 保持清晰的测试结构
- 应用 F.I.R.S.T. 原则 - 快速、隔离、可重复、自我验证、及时
- 将夹具放在模型附近 - 使用
#if DEBUG保护 - 将测试替身放在接口附近 - 使用
#if DEBUG保护 - 优先验证状态 - 比验证行为更简单,也更不易脆弱
- 使用参数化测试 - 高效测试多个输入
- 遵循测试金字塔 - 80% 单元测试、15% 集成测试、5% UI 测试
验证清单(编写测试时)
- 测试遵循 Arrange-Act-Assert 模式
- 测试名称描述行为,而非实现
- 夹具使用合理的默认值,而非随机值
- 测试替身保持精简(只 Stub 必要内容)
- 异步测试使用恰当的模式(async/await、confirmation)
- 测试运行迅速(模拟开销大的操作)
- 测试相互隔离(无共享状态)
- 测试可重复运行(不依赖不稳定的日期/时间)
评论
支持 Markdown