mcpField
T
test-modernizer

test-modernizer

github.com/superagents-lab测试与质量353703 次安装20 周前更新
代码仓库

关于 test-modernizer

test-modernizer 是用于更新现有 Swift 测试的技能,重点支持从 XCTest 迁移到 Swift Testing。它提供测试结构、断言、并发、跳过逻辑和附件的转换指导,并指出不适合迁移的情况。

主要功能

  1. 01从 XCTest 迁移到 Swift Testing
  2. 02转换测试结构与初始化逻辑
  3. 03替换断言和错误检查
  4. 04处理并行执行与共享状态
  5. 05转换跳过逻辑和附件

适用场景

  1. 01迁移现有 XCTest 测试
  2. 02改进 Swift Testing 测试结构
  3. 03将重复测试改为参数化测试

安装

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

$npx skills add https://github.com/superagents-lab/xcode27-skills --skill test-modernizer

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

详细介绍

测试现代化

适用情况:用户要求对测试进行现代化、更新、迁移、增强或转换。 在可能的情况下,应将 XCTest 迁移到 Swift Testing;对于现有的 Swift Testing 测试,应评估是否能通过采用较新的功能来改进结构。

不适用情况:用户要求从头编写新测试(没有现有的 XCTest 代码);用户只询问 XCTest 功能;用户只询问测试结果或如何运行测试;用户要更新测试以覆盖新功能,而不是更新测试本身;用户在调试测试失败,但没有提及迁移;用户有使用 XCUI* API 的 UI 自动化测试(这些测试无法迁移到 Swift Testing)。

迁移参考

导入

将 import XCTest 替换为 import Testing。如果文件包含混合测试内容,可以在增量迁移期间同时导入两者。

移除 import XCTest 时,请检查文件是否使用了 Foundation 类型(URL、CharacterSet、ProcessInfo、Data 等)。XCTest 会重新导出 Foundation,因此如有需要,请添加 import Foundation。

将测试类改为套件

移除 XCTestCase 继承。优先使用 struct 而非 class:

  • final class FoodTruckTests: XCTestCase { ... } -> struct FoodTruckTests { ... }
将 setUp/tearDown 改为 init/deinit

将 override func setUp() 替换为 init()(可以是 async throws)。将 override func tearDown() 替换为 deinit。如果需要 deinit,请使用 actor 或 final class 而非 struct(因为结构体没有 deinit)。将存储属性改为非隐式解包可选类型,并将其初始赋值从 setUp 移到内联初始化处;如果初始化较复杂,则移到初始化器中。

struct MyTests {
    var fixture = Fixture()
    mutating func `Fixture behaves as expected`() {
        #expect(fixture.doSomething())
    }
}

避免将实例变量移入函数体,这可能会造成冗余。Swift Testing 会在每次测试运行前重新调用初始化器。如果测试会修改具有值语义的实例变量,可能需要将测试函数标记为 mutating。

测试方法

将 test 名称前缀替换为 @Test 属性。如果生成的测试名称包含多个驼峰式单词,请使用原始标识符,并将测试名称写成句子形式。

  • func testEngineDoesNotStall() { ... } -> @Test func 发动机不会熄火() { ... }
  • func testIgnition() { ... } -> @Test func ignition() { ... }

测试函数可以是 async、throws 或 async throws,也可以通过 @MainActor 隔离到全局 Actor。

将断言改为期望

将测试从 XCTest 迁移到 Swift Testing 时,请使用以下对应关系:

XCTAssert(x)、XCTAssertTrue(x) -> #expect(x) XCTAssertFalse(x) -> #expect(!x) XCTAssertNil(x) -> #expect(x == nil) XCTAssertNotNil(x) -> #expect(x != nil) XCTAssertEqual(x, y) -> #expect(x == y) XCTAssertNotEqual(x, y) -> #expect(x != y) XCTAssertIdentical(x, y) -> #expect(x === y) XCTAssertNotIdentical(x, y) -> #expect(x !== y) XCTAssertGreaterThan(x, y) -> #expect(x > y) XCTAssertGreaterThanOrEqual(x, y) -> #expect(x >= y) XCTAssertLessThanOrEqual(x, y) -> #expect(x <= y) XCTAssertLessThan(x, y) -> #expect(x < y) try XCTUnwrap(x) -> try #require(x)

XCTAssertEqual(_:_:accuracy:) 没有直接对应项;请直接使用浮点数运算。

错误

如果错误类型符合 Equatable,且已知确切值,则优先检查具体的错误值。

XCTAssertThrowsError(try f())

->

#expect(throws: (any Error).self) {
    try f()
}
XCTAssertThrowsError(try f()) { error in
    XCTAssertEqual(error, specificError)
}

->

#expect(throws: specificError) {
    try f()
}
XCTAssertThrowsError(try f()) { error in
    // Check error
}

->

let error = #expect(throws: (any Error).self) {
    try f()
}
// Check error
XCTAssertNoThrow(try f())

->

#expect(throws: Never.self) {
    try f()
}
continueAfterFailure

默认情况下,continueAfterFailure 为 true,这意味着期望不会中止测试运行。 某些 XCTestCases 会设置 continueAfterFailure = false,这意味着 XCTAssert 系列函数 会抛出 Objective-C 异常,从而中止测试执行。

当测试方法设置 continueAfterFailure = false 时,后续所有断言都需要使用 try #require(x) 而不是 #expect(x),以保留此行为。添加 try #require(x) 时,请为受影响的方法添加 throws。

当在 setUp 中设置 continueAfterFailure = false 时,转换为 try #require(x) 必须应用于该类中 所有测试方法的所有断言。

将 Issue.record/XCTFail 转换为期望

在不会造成干扰的情况下,将 Issue.record 或 XCTFail 转换为 #expect 或 #require, 具体取决于测试是否会在之后退出(同时考虑 continueAfterFailure)。

在某些情况下,期望本身的来源已足以说明失败原因,因此注释会显得多余。

例如,应按如下方式转换以下结构:

guard let object = somethingOptional() else {
    Issue.record("Could not get object")
    return
}

guard object.isAvailable() else {
    Issue.record("Object not available")
    return
}

if !object.performOperation() {
    Issue.record("Failed to perform operation")
}

->

let object = try #require(somethingOptional(), "Could not get object")
try #require(object.isAvailable())
#expect(object.performOperation())
将异步期望转换为确认

将 XCTestExpectation + fulfill() + await fulfillment(of:) 替换为 confirmation():

// Before
let exp = expectation(description: "...")
handler = { exp.fulfill() }
doWork()
await fulfillment(of: [exp])

// After
await confirmation("...") { confirm in
    handler = { confirm() }
    doWork()
}

对于 assertForOverFulfill = false 且设置了 expectedFulfillmentCount 的情况,请使用范围: await confirmation("...", expectedCount: 10...) { confirm in ... }

跳过测试

将 XCTSkipIf/XCTSkipUnless 替换为测试或套件上的特征:

  • try XCTSkipIf(condition) -> @Test(.disabled(if: condition))
  • try XCTSkipUnless(condition) -> @Test(.enabled(if: condition))

在测试中途将 throw XCTSkip("reason") 替换为 try Test.cancel("reason")。

如果跳过测试的条件检查的是 OS 版本或平台可用性,请在测试函数上添加 @available 属性,而不是使用 .enabled(if:)。

已知问题

将 XCTExpectFailure("...", ...) { ... } 替换为 withKnownIssue("...") { ... }。

对于间歇性故障,将 .nonStrict() 选项(或简写参数 strict: false)替换为 isIntermittent: true。

对于条件/匹配,请使用 when: 和 matching: 参数:

withKnownIssue("...") {
    try riskyOperation()
} when: {
    shouldExpectFailure
} matching: { issue in
    issue.error != nil
}
并发与串行执行

XCTest 默认在主 Actor 上同步运行测试,并在套件内按顺序执行。Swift Testing 会在任意任务上并行运行所有测试函数。只有当测试在 XCTest 中明确依赖主 Actor 隔离时,才添加 @MainActor;如果测试依赖共享状态,则添加 @Suite(.serialized)。

附件

将 XCTAttachment + self.add(attachment) 替换为 Attachment.record(value)。所附类型必须遵循 Attachable(导入 Foundation 后,Codable 和 NSSecureCoding 类型会自动遵循该协议)。

现代化指南

  • 从 XCTest 迁移时,每次迁移一个测试类。迁移期间,一个文件中可以同时包含 XCTest 和 Swift Testing 测试。
  • 除非需要 deinit(tearDown),否则优先使用 struct 编写测试套件;如果需要,则使用 actor 或 final class。
  • 添加 @Test 时,从方法名中移除 test 前缀。对于较长且读起来像句子的测试名称,使用原始标识符语法以提升可读性,例如:@Test func Authenticate, fetch summary, then check count() { ... }。
  • 同时检查现有的 @Test 函数中是否有由多个单词组成的驼峰命名,并将其改为句子式的原始标识符。
  • 仅对由多个单词组成且读起来像句子的名称使用原始标识符语法。
  • 迁移 setUp 时,将隐式解包可选属性转换为非可选属性,并在属性声明处初始化;如果初始化过程复杂、可能抛出错误或为异步,则在 init 中初始化。
  • 查找可以转换为 #expect 或 #require 的显式 XCTFail/Issue.record 调用。
  • 不要将 try #require 调用改为 #expect;这样会改变测试行为。
  • 仅当测试明确依赖 XCTest 的隐式主 Actor 隔离时,才添加 @MainActor。不要不必要地添加。
  • 查找遍历输入的测试,或逻辑相同且重复较多的测试,并将其转换为使用 @Test(arguments:) 的参数化测试。
  • 对于测试间共享可变状态的测试套件,添加 @Suite(.serialized),并考虑使用 actor 或 class 替代 struct。
  • 不要使用以下划线开头的符号,例如 #_sourceLocation;仅使用公共 API。对于源代码位置,始终使用完整的 SourceLocation(fileID:filePath:line:column:) 初始化器。

评论

支持 Markdown