@Test
把函数标记为测试。可以是全局函数,也可以是方法;名字不需要 test 前缀。第一个参数可以传显示名称。
@Test func engineWorks() {
...
}
@Test("Food truck exists") func foodTruckExists() { ... }Swift 6.0,Xcode 16.0 Apple 页面
Swift Testing 需要 Swift 6.0 和 Xcode 16 或更高。按 Apple 的迁移指南:XCTAssertEqual(a, b) 改为 #expect(a == b),XCTAssertNil(x) 改为 #expect(x == nil),try XCTUnwrap(x) 改为 try #require(x),XCTFail("…") 改为 Issue.record("…"),setUp 改为 init,tearDown 改为 deinit,expectation 改为 confirmation,XCTSkipIf 改为 .disabled(if:) 特征。UI 测试和性能测试仍用 XCTest。在下方粘贴测试类即可转换,无法转换的行会被标出并说明原因。下面还有 Apple 的完整对照表,以及 Swift Testing 每项功能的参考。
粘贴含有一个或多个 XCTestCase 类的 Swift 文件。转换在本页内完成,不会上传任何内容。
Swift Testing 版本
在上方粘贴 XCTestCase 类,即可看到 Swift Testing 版本。
来自 Apple 的《Migrating a test from XCTest》。前几行就是 Apple 的断言对照表原样。 Apple 页面
| 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) | |
| XCTAssertThrowsError(try f()) | #expect(throws: (any Error).self) { try f() } | |
| XCTAssertThrowsError(try f()) { error in … } | let error = #expect(throws: (any Error).self) { try f() } | |
| XCTAssertNoThrow(try f()) | #expect(throws: Never.self) { try f() } | |
| try XCTUnwrap(x) | try #require(x) | |
| XCTFail("…") | Issue.record("…") | |
| XCTAssertEqual(x, y, accuracy: e) | — | 没有对应写法。Apple 建议用 swift-numerics 中的 isApproximatelyEqual()。 |
| import XCTest | import Testing | 迁移期间一个文件可以同时导入两者。 |
| class FoodTruckTests: XCTestCase | struct FoodTruckTests | 去掉 XCTestCase 继承。Apple 建议用结构体或 actor,方便编译器检查并发安全。 |
| func testEngineWorks() | @Test func engineWorks() | 不再需要 test 前缀。XCTest 在主 actor 上运行同步测试,Swift Testing 不会,需要时请加 @MainActor。 |
| override func setUp() async throws | init() async throws | |
| override func tearDown() | deinit | 需要类或 actor(final class FoodTruckTests)。 |
| continueAfterFailure = false | try #require(…) | |
| try XCTSkipIf(x) / try XCTSkipUnless(x) | @Test(.disabled(if: x)) / @Test(.enabled(if: x)) | |
| throw XCTSkip("…") | try Test.cancel("…") | 需要 Swift 6.3、Xcode 26.4 或更高。 |
| XCTExpectFailure("…") { … } | withKnownIssue("…") { … } | |
| XCTExpectFailure("…", options: .nonStrict()) { … } | withKnownIssue("…", isIntermittent: true) { … } | |
| options.isEnabled / options.issueMatcher | withKnownIssue { … } when: { … } matching: { issue in … } | |
| XCTExpectFailure(_:options:) (no closure) | — | 没有直接对应写法。把整个测试体包进 withKnownIssue。 |
| expectation(description:) / fulfill() / fulfillment(of:) | await confirmation("…") { done in … done() } | 确认事件在闭包运行期间发生。使用范围次数需要 Swift 6.1。 |
| expectedFulfillmentCount = 10 | confirmation(expectedCount: 10) | |
| assertForOverFulfill = false | confirmation(expectedCount: 10...) | |
| (tests in a class run one after another) | @Suite(.serialized) | Swift Testing 默认并行运行套件里的测试。 |
| XCTAttachment + add(_:) | Attachment.record(…) | |
| XCUIApplication (UI tests) | — | 保留为 XCTest。Apple:Xcode 继续用 XCTest 编写 UI 测试。 Apple 页面 |
| measure { … } (performance tests) | — | 保留为 XCTest。Apple 的性能测试使用 XCTest 的 measure(_:)。 Apple 页面 |
每一项的作用、Apple 的示例,以及 Apple 在该符号页面上写明的最早 Swift 和 Xcode 版本。
把函数标记为测试。可以是全局函数,也可以是方法;名字不需要 test 前缀。第一个参数可以传显示名称。
@Test func engineWorks() {
...
}
@Test("Food truck exists") func foodTruckExists() { ... }Swift 6.0,Xcode 16.0 Apple 页面
把测试放进一个类型里分组。任何含有 @Test 方法的类型都是套件;用 @Suite 可以加显示名称或特征。
@Suite struct FoodTruckTests {
@Test func engineWorks() { ... }
}Swift 6.0,Xcode 16.0 Apple 页面
检查一个条件,失败时记录看到的值。测试会继续运行。
#expect(calculator.total(of: [3, 3]) == 7)
// Expectation failed: calculator.total(of: [3, 3]) == 7Swift 6.0,Xcode 16.0 Apple 页面
与 #expect 相同,但失败时抛错并停止测试。用于可选值时,会解包,或者停止测试。
let customer = try #require(Customer(id: 123))
try #require(engine.parts.first != nil)Swift 6.0,Xcode 16.0 Apple 页面
检查代码会抛错(或者用 Never.self 检查不会抛错)。返回值就是抛出的错误。
#expect(throws: (any Error).self) { try f() }
let error = #expect(throws: (any Error).self) { try f() }
#expect(throws: Never.self) { try f() }Swift 6.0,Xcode 16.0 Apple 页面
无条件记录一次失败,用在没有合适条件的地方,比如 guard 的 else 分支。
guard case .electric = engine else {
Issue.record("Engine is not electric")
return
}Swift 6.0,Xcode 16.0 Apple 页面
确认某个事件在闭包运行期间发生。事件发生时像调用函数一样调用 confirmation。默认要求恰好一次。
await confirmation("…") { soldFood in
FoodTruck.shared.eventHandler = { event in
if case .soldFood = event { soldFood() }
}
await Customer().buy(.soup)
}Swift 6.0,Xcode 16.0 Apple 页面
expectedCount 可以是数字,也可以是带下限的范围,比如 10...(至少十次)或 0..<1000。
await confirmation("…", expectedCount: 10...) { soldFood in
...
}Swift 6.1,Xcode 16.3 Apple 页面
把闭包里的失败标记为已知问题,不会让测试失败。isIntermittent: true 允许某些运行通过。
withKnownIssue("Grill is out of fuel") {
try FoodTruck.shared.grill.start()
}
withKnownIssue("Grill may need fuel", isIntermittent: true) { ... }Swift 6.0,Xcode 16.0 Apple 页面
只在条件为真时运行测试,否则在开始前跳过。
@Test("Ice cream is cold", .enabled(if: Season.current == .summer))
func isCold() async throws { ... }Swift 6.0,Xcode 16.0 Apple 页面
总是跳过测试,或在条件为真时跳过。备注会显示在测试结果里。
@Test("Food truck sells burritos", .disabled("We only sell Thai cuisine"))
func sellsBurritos() async throws { ... }
@Suite(.disabled(if: CashRegister.isEmpty))
struct FoodTruckTests { ... }Swift 6.0,Xcode 16.0 Apple 页面
测试运行超过时限就算失败。只能按分钟设置,至少一分钟。用在套件上时,对每个测试分别生效。
@Test(.timeLimit(.minutes(60)))
func serve100CustomersInOneHour() async { ... }Swift 6.0,Xcode 16.0 还需要 iOS 16, macOS 13, tvOS 16, watchOS 9。 Apple 页面
给测试加标签,方便筛选和分组。用 @Tag 在 Tag 的扩展里声明标签。
extension Tag {
@Tag static var legallyRequired: Self
}
@Test("Vendor's license is valid", .tags(.legallyRequired))
func licenseValid() { ... }Swift 6.0,Xcode 16.0 Apple 页面
让套件里的测试(或参数化测试的各个用例)逐个运行,而不是并行运行。
@Suite(.serialized)
class RefrigeratorTests {
@Test func lightComesOn() throws { ... }
@Test func lightGoesOut() throws { ... }
}Swift 6.0,Xcode 16.0 Apple 页面
把测试与缺陷报告关联,可以用网址、编号或两者,还可加标题。
@Test("Food truck engine works", .bug("https://www.example.com/issues/12345"))
func engineWorks() async { ... }
@Test(.bug(id: "12345", "Forgot to buy more napkins"))
func hasNapkins() async { ... }Swift 6.0,Xcode 16.0 Apple 页面
对每个值各运行一次测试。每个值是一个独立的测试用例,失败时会指出是哪个输入。用例默认并行运行。
@Test("All foods available", arguments: Food.allCases)
func foodAvailable(_ food: Food) async throws {
let foodTruck = FoodTruck(selling: food)
#expect(await foodTruck.cook(food))
}Swift 6.0,Xcode 16.0 Apple 页面
测试函数可以是 async 和 throws 的,可以直接 await。
@Test func priceLookupYieldsExpectedValue() async {
let mozarellaPrice = await unitPrice(for: .mozarella)
#expect(mozarellaPrice == 3)
}Swift 6.0,Xcode 16.0 Apple 页面
提前结束正在运行的测试且不算失败,用来替代 throw XCTSkip。
if drawer.isEmpty {
try Test.cancel("Cash register is empty")
}Swift 6.3,Xcode 26.4 Apple 页面
把一个值附加到测试结果里,用来替代 XCTAttachment。
let tortilla = Tortilla(diameter: .large)
Attachment.record(tortilla)Swift 6.2,Xcode 26.0 Apple 页面
Apple 的页面,读取于 2026 年 11 月 1 日: