Swift Testing vs XCTest 对比

Apple 全新的开源测试库——基于宏,与 Swift Concurrency 原生集成

VS
XCTest

拥有约 13 年历史、久经实战检验的框架——UI 测试和性能测试的唯一选择

15 分钟阅读iOS

快速结论

趋势明确,但“一次性全部迁移”是不必要的风险。新的单元测试和集成测试请使用 Swift Testing 编写——它带来更少的代码、自动的值捕获、内置的参数化测试和并行执行。UI 测试和性能测试请继续留在 XCTest 中,这是 Apple 官方文档的建议。由于两个框架可以在同一个测试目标中共存,加上 Swift 6.4 带来了跨框架的问题报告能力,全量迁移并无必要——按文件逐步推进即可。

Swift TestingXCTest
阅读完整结论

评分对比

图表加载中…

详细评分

详细评分: Swift Testing XCTest ——按类别打分,满分 10 分
分类Swift TestingXCTest
性能
8/10
8/10
学习难易度
7/10
6/10
生态系统
6/10
8/10
社区
6/10
8/10
就业市场
5/10
7/10
面向未来
9/10
6/10

优缺点

Swift Testing

优点

  • 通过 #expect 和 #require 宏,一行代码即可自动捕获数值,无需记忆各种独立的断言函数族
  • 内置参数化测试支持(@Test(arguments:))——无需编写循环,单个测试即可覆盖多种场景
  • 默认并行运行;基于 task group 的进程内并行执行让测试套件运行更快
  • 原生支持 async throws 测试函数——与 Swift Concurrency 无缝集成
  • 支持测试取消(Test.cancel())、警告级别的问题记录,以及图像/Transferable 附件支持(Swift 6.3-6.4)
  • 除 Apple 平台外,通过官方工具链也能在 Linux 和 Windows 上运行
  • 开源(Apache 2.0)——在 GitHub 上积极开发,欢迎社区贡献
  • 以 FB 为前缀的 bug 标识符可直接关联到 Apple Feedback Assistant

缺点

  • 不支持 UI 自动化(XCUIApplication/XCUIElement)和性能测试——这些场景必须使用 XCTest
  • 无法用于低于 Xcode 16 / Swift 6 工具链的项目,旧版 Xcode 中不存在
  • 生态系统相对年轻——第三方库和 StackOverflow 答案的积累不如 XCTest 深厚
  • #expect 内部抛出的错误可能会静默中断后续的期望检查,需要用 #require 或 do/catch 加以规范
  • 默认并行执行可能会暴露出旧测试代码中因共享状态(数据库 fixture、单例)导致的竞态条件

最适合

新建的 iOS/macOS/Linux 单元测试和集成测试需要大量输入组合的参数化测试场景以 Swift Concurrency(async/await)为主的代码库希望获得快速并行测试套件的成长型项目跨平台 Swift 包(包括 Linux/Windows CI)

XCTest

优点

  • 自 Xcode 5(2013 年)以来一直是 Apple 官方测试框架——在企业级项目中久经验证的稳定性
  • 通过 XCUITest 实现的 UI 自动化和性能测试目前只存在于 XCTest 中
  • 凭借约 13 年积累的 StackOverflow、博客和书籍内容,拥有最深厚的知识库
  • 与 Xcode 的测试报告(xcresult)、Test Plan 和 CI 集成完全融合
  • 在包含 Objective-C 的混合代码库中仍是唯一强制选项
  • 在大型团队中是人人熟悉的通用语法——新人上手成本低
  • 针对每种场景都有独立的断言函数族(布尔/空值/相等/可比较/错误),清晰且可预测
  • 内置基于 XCTMetric 的性能测试度量基础设施

缺点

  • 没有内置的参数化测试 API——需要用循环或辅助函数来模拟实现
  • XCTAssert* 函数族写法冗长;不具备 Swift Testing 那样的自动值捕获能力
  • 在测试函数层级没有进程内 task-group 并行机制;并行执行是在模拟器/进程层级管理的
  • 异步支持是后来加入的(XCTestExpectation/wait(for:))——与 Swift Concurrency 的结合不如 Swift Testing 自然
  • Apple 在 WWDC24 上的定位已将 XCTest 的角色限定在 UI 自动化、性能测试以及只能用 Objective-C 编写的测试上——新的单元测试体验正在 Swift Testing 中发展
  • 源自 Objective-C 的 API 表面对新入门的 Swift 开发者来说学习负担更重

最适合

使用 XCUITest 进行的 UI 自动化测试基于 XCTMetric 的性能测试包含 Objective-C 代码或需要支持旧版 Xcode 的项目长期运行的大型遗留测试套件需要团队统一、成熟测试规范的企业级项目

代码对比

Swift Testing
// Swift Testing — 参数化 async 单元测试
import Testing
@testable import PaymentKit

@Suite("价格计算")
struct PriceCalculatorTests {

    @Test("有效优惠码能正确计算折扣",
          arguments: [
              ("SAVE10", 100.0, 90.0),
              ("SAVE20", 100.0, 80.0),
              ("NONE", 100.0, 100.0)
          ])
    func discountIsApplied(code: String, base: Double, expected: Double) async throws {
        let calculator = PriceCalculator()
        let result = try await calculator.applyDiscount(code: code, to: base)
        #expect(result == expected, "\(code) 期望 \(expected),实际 \(result)")
    }

    @Test("无效优惠码会抛出错误")
    func invalidCouponThrows() async throws {
        let calculator = PriceCalculator()
        await #expect(throws: CouponError.invalid) {
            try await calculator.applyDiscount(code: "XXX", to: 100.0)
        }
    }

    @Test("需要购物车总额", .disabled("购物车服务尚未迁移"))
    func cartTotalRequired() async throws {
        let calculator = PriceCalculator()
        try #require(calculator.cartTotal > 0)
    }
}
XCTest
// XCTest — 同一场景(价格计算)+ UI 测试
import XCTest
@testable import PaymentKit

final class PriceCalculatorTests: XCTestCase {
    var calculator: PriceCalculator!

    override func setUpWithError() throws {
        calculator = PriceCalculator()
    }

    func testDiscountIsAppliedForKnownCoupons() async throws {
        let cases: [(String, Double, Double)] = [
            ("SAVE10", 100.0, 90.0),
            ("SAVE20", 100.0, 80.0),
            ("NONE", 100.0, 100.0)
        ]
        for (code, base, expected) in cases {
            let result = try await calculator.applyDiscount(code: code, to: base)
            XCTAssertEqual(result, expected, "优惠码:\(code)")
        }
    }

    // XCTAssertThrowsError 是同步的;在 async 调用中使用 do/catch + XCTFail
    func testInvalidCouponThrows() async throws {
        do {
            _ = try await calculator.applyDiscount(code: "XXX", to: 100.0)
            XCTFail("期望抛出错误")
        } catch {
            XCTAssertEqual(error as? CouponError, .invalid)
        }
    }
}

// UI 测试 — 仅能通过 XCTest/XCUITest 实现
final class CheckoutUITests: XCTestCase {
    func testApplyCouponButtonUpdatesTotal() throws {
        let app = XCUIApplication()
        app.launch()
        app.textFields["couponField"].tap()
        app.textFields["couponField"].typeText("SAVE10")
        app.buttons["applyCouponButton"].tap()
        XCTAssertEqual(app.staticTexts["totalLabel"].label, "$90.00")
    }
}

结论

趋势明确,但“一次性全部迁移”是不必要的风险。新的单元测试和集成测试请使用 Swift Testing 编写——它带来更少的代码、自动的值捕获、内置的参数化测试和并行执行。UI 测试和性能测试请继续留在 XCTest 中,这是 Apple 官方文档的建议。由于两个框架可以在同一个测试目标中共存,加上 Swift 6.4 带来了跨框架的问题报告能力,全量迁移并无必要——按文件逐步推进即可。

获取免费咨询
常见问题

常见问题

没有。Swift Testing 是 Apple 为新的单元测试和集成测试推荐的默认选择,但 XCTest 并未被废弃;XCUITest(UI 自动化)和 XCTMetric(性能测试)目前仍然只存在于 XCTest 中。自 Swift 6.4 起,两个框架可以在同一个测试目标中共存,并支持跨框架的问题报告。

相关博客文章

查看全部文章

相关项目

查看全部项目
全部对比