单元测试与集成测试对比
iOS 测试策略的基石:是选择让单元隔离的单元测试,还是选择验证组件间交互的集成测试?如何正确搭建测试金字塔?
Apple 全新的开源测试库——基于宏,与 Swift Concurrency 原生集成
拥有约 13 年历史、久经实战检验的框架——UI 测试和性能测试的唯一选择
趋势明确,但“一次性全部迁移”是不必要的风险。新的单元测试和集成测试请使用 Swift Testing 编写——它带来更少的代码、自动的值捕获、内置的参数化测试和并行执行。UI 测试和性能测试请继续留在 XCTest 中,这是 Apple 官方文档的建议。由于两个框架可以在同一个测试目标中共存,加上 Swift 6.4 带来了跨框架的问题报告能力,全量迁移并无必要——按文件逐步推进即可。
| 分类 | Swift Testing | XCTest |
|---|---|---|
| 性能 | 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 — 参数化 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 — 同一场景(价格计算)+ 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 起,两个框架可以在同一个测试目标中共存,并支持跨框架的问题报告。