mcpField
S
swift-testing

swift-testing

github.com/dpearson2699测试与质量1.2k4.4k 次安装13 周前更新
代码仓库

关于 swift-testing

面向 Swift 开发者的现代单元测试指南,介绍 Swift Testing 的测试声明、断言、并行执行和 XCTest 迁移边界,并说明进阶 API 的版本要求。

主要功能

  1. 01使用 @Test 声明测试
  2. 02通过 #expect 与 #require 断言
  3. 03套件、标签与测试特征
  4. 04并行执行与状态隔离
  5. 05与 XCTest 共存及迁移
  6. 06版本门控 API 指引

适用场景

  1. 01为 Swift 代码编写行为测试
  2. 02测试异步操作与错误路径
  3. 03隔离访问共享外部状态的测试
  4. 04逐套件迁移 XCTest 单元测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-testing

安装后在 Claude Code 中输入/swift-testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
testing-advanced.md
testing-patterns.md

详细介绍

Swift Testing

Swift Testing 是 Swift 的现代化测试框架(Xcode 16+、Swift 6+)。新增单元测试时优先使用它。迁移尚未完成时保留 XCTest;UI 自动化、性能 API、Objective-C 异常测试和常见的快照测试工具则使用 XCTest。

目录


基础测试

import Testing

@Test("User can update their display name")
func updateDisplayName() {
    var user = User(name: "Alice")
    user.name = "Bob"
    #expect(user.name == "Bob")
}

@Test 特征

@Test("Validates email format")                                    // display name
@Test(.tags(.validation, .email))                                  // tags
@Test(.disabled("Server migration in progress"))                   // disabled
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil)) // conditional
@Test(.bug("https://github.com/org/repo/issues/42"))               // bug reference
@Test(.timeLimit(.minutes(1)))                                     // time limit
@Test("Timeout handling", .tags(.networking), .timeLimit(.seconds(30))) // combined

#expect 和 #require

// #expect records failure but continues execution
#expect(result == 42)
#expect(name.isEmpty == false)
#expect(items.count > 0, "Items should not be empty")

// #expect with error type checking
#expect(throws: ValidationError.self) {
    try validate(email: "not-an-email")
}

// #expect with specific error value
#expect {
    try validate(email: "")
} throws: { error in
    guard let err = error as? ValidationError else { return false }
    return err == .empty
}

// #require records failure AND stops test (like XCTUnwrap)
let user = try #require(await fetchUser(id: 1))
#expect(user.name == "Alice")

// #require for optionals -- unwraps or fails
let first = try #require(items.first)
#expect(first.isValid)

规则:后续断言依赖某个值时,使用 #require。对于彼此独立的检查,使用 #expect。

@Suite 和测试组织

有关套件组织、确认模式、已知问题处理和执行模型的详情,请参阅 references/testing-patterns.md。

执行模型

Swift Testing 默认并行运行测试。除非明确设计为如此,否则不要假设测试顺序、套件实例共享,或对可变状态具有独占访问权。

@Suite(.serialized)
struct KeychainTests {
    @Test func storesToken() throws { /* ... */ }
    @Test func deletesToken() throws { /* ... */ }
}

当测试或套件因访问共享外部状态而必须逐个运行时,使用 .serialized。它不会让该范围之外的无关测试也按串行方式运行。

规则:

  • 每项测试都必须自行设置状态。
  • 共享可变全局变量属于缺陷,除非它们受到保护或明确采用串行方式运行。
  • @Suite(.serialized) 用于独占执行,而不是表达测试之间的逻辑顺序。
  • 如果测试依赖执行顺序,请将它们合并为一项测试,或将执行序列移入共享辅助代码。

XCTest 迁移边界

Swift Testing 单元测试不会继承自 XCTestCase。在独立函数或 struct、class、actor 等套件类型的方法上声明 @Test;不需要实例 fixture 时,使用 static 或 class 方法。

迁移期间,XCTest 和 Swift Testing 可以共存。每次迁移一个文件或套件,并比较测试发现数、通过数、失败数和跳过数;UI 自动化、性能基准测试和常见快照测试流程则继续使用 XCTest/XCUITest 或快照工具。如果这样能让运行器预期更清晰,请将它们分隔到不同文件或目标中。

对于 Xcode 27 时代的混合辅助工具,应检查已配置的互操作模式,而不是声称跨框架 API 禁止使用。旧版测试计划继承 limited;新项目使用 complete;此外还有 strict 和 none。迁移期间优先使用 complete 或 strict;SwiftPM 需要时使用 SWIFT_TESTING_XCTEST_INTEROP_MODE。有关模式矩阵和工具链版本限制,请参阅 references/testing-advanced.md。

不要机械地将每个 XCTest 断言都替换为 #expect;请根据以下迁移默认规则,保留必要的解包和无条件失败:

  • XCTAssert* -> #expect(...)
  • XCTUnwrap 或后续检查所需的任何值 -> try #require(...)
  • XCTFail("...") 或手动记录的无条件问题 -> Issue.record("...")
  • UI 测试、性能基准测试和常见快照测试流程继续使用 XCTest/XCUITest 或快照工具。
  • 将 @available 用于单个 @Test 函数,而不是套件类型或其外层类型。

迁移示例请参阅 references/testing-patterns.md;Swift/Xcode 版本限制请参阅 references/testing-advanced.md。

已知问题

标记预期失败,以免它们导致测试失败:

withKnownIssue("Propane tank is empty") {
    #expect(truck.grill.isHeating)
}

// Intermittent / flaky failures
withKnownIssue(isIntermittent: true) {
    #expect(service.isReachable)
}

// Conditional known issue
withKnownIssue {
    #expect(foodTruck.grill.isHeating)
} when: {
    !hasPropane
}

如果没有记录任何已知问题,Swift Testing 会记录一条独立问题,通知你该问题可能已经解决。

其他模式

参数化测试、标签和测试套件、异步测试、特征及执行模型的详细信息,请参阅 references/testing-patterns.md。

测试附件

将诊断数据附加到测试结果中,以便调试失败。完整示例请参阅 references/testing-patterns.md。

@Test func generateReport() async throws {
    let report = try generateReport()
    Attachment.record(report.data, named: "report.json")
    #expect(report.isValid)
}

对于图像附件及其工具链门槛,请使用 版本门控 API 中的规范表格。

退出测试

在受支持的运行时上测试调用 exit()、fatalError() 或 preconditionFailure() 的代码。更正退出测试代码时,请注明 版本门控 API 中的确切门槛。

@Test func invalidInputCausesExit() async {
    await #expect(processExitsWith: .failure) {
        processInvalidInput()  // calls fatalError()
    }
}

版本门控 API

对于高级 API,请在更正旁注明确切的工具链和运行时门槛。这是规范摘要;详细矩阵和示例请参阅 references/testing-advanced.md。

@Test func exitsWithCapturedCode() async {
    let expectedCode: Int32 = 42
    await #expect(processExitsWith: .failure) { [expectedCode] in
        exit(expectedCode)
    }
}
要更正的用户代码当前指导
#expect(exitsWith:)使用 await #expect(processExitsWith: .failure) { ... }。退出测试需要 Swift 6.2 / Xcode 26.0 或更高版本,且受 macOS、Linux、FreeBSD、OpenBSD 和 Windows 运行时目标支持,不支持 iOS、tvOS 或 watchOS。对于 iOS 应用目标,请通过较小的非退出 API 或受支持的宿主 / 工具目标测试致命错误路径逻辑。
退出测试闭包读取外部值添加显式捕获列表,例如 { [expectedCode] in ... }。退出测试捕获列表需要 Swift 6.3 编译器;捕获的值必须符合 Sendable 和 Codable。
在等待任务的测试中使用 Test.cancel()将测试声明为 async throws,并调用 try Test.cancel("reason")。Test.cancel(_:) 需要 Swift 6.3 / Xcode 26.4 时代的支持。
Issue.record(..., severity: .warning)使用 Issue.record("message", severity: .warning)。警告严重级别会报告问题,但不会导致测试失败;此功能需要 Swift 6.3 / Xcode 26.4 时代的支持。
Attachment(image, named:).record()使用 Attachment.record(image, named: "name", as: .png)。导入 Testing 以及相关图像框架;Apple 平台的图像值包括 UIImage、CGImage、CIImage 和 NSImage。记录图像附件需要 Swift 6.3 / Xcode 26.4 时代的支持。

常见错误

  1. 测试实现,而非行为。 测试代码的实际行为,而不是实现方式。
  2. 没有错误路径测试。 如果函数可能抛出错误,就测试错误路径。
  3. 异步测试不稳定或使用休眠。 使用 confirmation、时钟注入或并发原语,而不是休眠。
  4. 测试之间共享可变状态。 每个测试都通过 @Suite 中的 init() 设置自己的状态。
  5. UI 测试中缺少无障碍标识符。 XCUITest 查询依赖这些标识符。
  6. 未测试取消行为。 如果代码支持取消 Task,请确认它能干净地取消。
  7. 迁移边界不清晰。 遵循 XCTest 迁移边界,不要将任一框架视为只能全盘采用或完全弃用。
  8. 测试之间共享非 Sendable 辅助项。 让共享辅助项符合 Sendable;使用 @MainActor 标注依赖 MainActor 的测试代码。
  9. 将串行执行视为有序执行。 默认情况下,测试会并行运行;.serialized 可保护独占状态,但不会让一个测试为另一个测试提供输入。

审查清单

  • 所有新增测试都使用 Swift Testing(@Test、#expect),而非 XCTest 断言
  • 测试名称描述行为(fetchUserReturnsNilOnNetworkError,而非 testFetchUser)
  • 错误路径有专门的测试
  • 异步测试使用 confirmation(),而非 Task.sleep
  • 对重复的变体使用参数化测试
  • 应用标签以便筛选(.critical、.slow)
  • 模拟对象遵循协议,而非继承具体类型
  • 测试之间没有共享的可变状态
  • 测试不依赖声明顺序或共享的套件实例
  • 仅在状态确实互斥时使用 .serialized,而非将其用于模拟工作流顺序
  • 对可取消的异步操作测试取消行为

参考资料

评论

支持 Markdown