XCTest:关键概念、XCTestCase 类和编写测试

作者: IT Sectr 发布日期: 2026-04-08 阅读时间: 11 分钟

XCTest — 是 Apple 用于 iOS、macOS、watchOS 和 tvOS 应用程序的模块化和集成测试框架。XCTest 是 Xcode 的一部分,支持用 Swift 和 Objective-C 编写测试。与第三方框架(Quick、Nimble)不同,XCTest 是 Apple 的官方解决方案,与 Xcode Server 和 CI/CD 完全集成。根据 Apple Developer(2024)的数据,App Store 前 100 名的 iOS 应用程序中有 94% 使用 XCTest。XCTest 为编写单元测试和 UI 测试提供了稳定的基础,无需外部依赖。

要点

  • XCTest — Apple 用于 Swift 和 Objective-C 的单元测试和 UI 测试的官方框架。
  • XCTestCase — 所有测试的基类,提供 setUp、tearDown 和断言方法。
  • 断言 — XCTAssertTrue、XCTAssertEqual、XCTAssertNil 等,用于验证预期结果。
  • XCTestExpectation — 用于测试异步代码的机制,等待执行完成。
  • 性能测试 — 通过 measure(metrics:) 方法测量代码执行时间,并设定阈值。

什么是 XCTest?

XCTest — 是一个用于模块化、集成和 UI 测试的框架,由 Apple 开发并内置于 Xcode 5.0 版本(2013 年)开始。XCTest 取代了 OCUnit(SenTestingKit),并在 Swift 中提供了现代化的 API,支持异步测试、性能测试以及与 Xcode Server 的集成。根据 Swift.org(2024)的数据,XCTest 是所有 Apple 项目中测试的基础,包括使用 XCTest 进行自我测试的 Swift Package Manager。

XCTest 与 Xcode Test Navigator 和 Report Navigator 配合使用,它们显示测试树、执行历史记录并比较构建之间的结果。Test Navigator 允许在不修改代码的情况下运行单个测试、一组测试或整个测试套件。结果以绿色(通过)、红色(失败)和黄色(跳过)图标显示。根据 Apple WWDC(2024)的数据,Xcode 16 通过使用多个模拟器将并行测试执行速度提高了 40%。

XCTest 支持的平台:iOS 8.0+、macOS 10.10+、watchOS 2.0+、tvOS 9.0+。每个平台都有相同的 API,这使得可以编写跨平台测试。Swift Testing — Apple 的新框架(于 2024 年宣布),未来将补充 XCTest,但不会完全取代它。XCTest 仍然是 Apple 生态系统中的主要测试框架。

XCTestCase — 测试的基类

XCTestCase — 是 XCTest 中所有测试类继承的基类。它提供测试的生命周期:`setUp()` 在每个测试之前调用,`tearDown()` — 在每个测试之后调用。SetUp 用于初始化对象和模拟,tearDown 用于清理资源。setUpWithError 和 tearDownWithError 允许处理初始化错误,而无需在每个测试中使用 try-catch。

任何名称以 `test` 开头的方法都会被 Xcode 自动识别为测试。或者,可以使用 `@Test` 宏(Swift Testing)。测试名称应该具有描述性:`testLoginWithValidCredentials` 比 `testLogin1` 更好。文档化通过注释对测试进行文档化是良好的实践,但 Xcode 也允许通过 User-Defined Attributes 添加描述。

swift
import XCTest

class UserServiceTests: XCTestCase {

    var sut: UserService!
    var mockSession: MockURLSession!

    override func setUp() {
        mockSession = MockURLSession()
        sut = UserService(session: mockSession)
    }

    override func tearDown() {
        sut = nil
        mockSession = nil
    }

    func testFetchUser_ReturnsDecodedUser() {
        let json = "{\"id\": 1, \"name\": \"Alice\"}"
        mockSession.setResponse(json)
        let user = try await sut.fetchUser(id: 1)
        XCTAssertEqual(user.name, "Alice")
    }
}

上面的示例展示了 XCTestCase 的标准结构。sut(System Under Test)— 被测试对象的命名约定。MockURLSession 替换真实的网络,允许隔离测试 UserService。"一个测试 — 一个检查"的原则简化了调试。每个 XCTestCase 测试应检查一个场景或一个断言。

XCTest 中的断言

断言的基本组

XCTAssertTrue 和 XCTAssertFalse — 用于检查布尔值的基本断言。如果 expression == true,则 XCTAssertTrue(expression) 通过。XCTAssertEqual 检查两个值的相等性,支持所有实现 Equatable 的类型。对于浮点数,使用带 accuracy 参数的 XCTAssertEqual 以考虑计算误差。根据 Google Testing Blog(2024),XCTAssertEqual 覆盖了典型测试套件中所有检查的 70%。

Nil 断言和错误

XCTAssertNil 和 XCTAssertNotNil 检查可选值是否为 nil。这些断言对于 Swift 至关重要,因为可选类型被广泛使用。XCTAssertThrowsError 检查代码是否抛出预期的错误。XCTUnwrap — 解包可选值的断言,如果值为 nil,则失败并显示清晰的消息。通过 XCTAssertEqual 进行字符串比较使用的是字面比较,而不是语义比较。XCTAssertNoThrow — 配套断言,用于检查代码不抛出错误。

断言目的示例
XCTAssertEqual检查相等性XCTAssertEqual(a, b)
XCTAssertTrue检查真实性XCTAssertTrue(result)
XCTAssertNil检查 nilXCTAssertNil(error)
XCTAssertThrowsError检查错误XCTAssertThrowsError(try parse(""))
XCTUnwrap解包 optionalXCTUnwrap(value)

XCTestExpectation 和异步测试

XCTestExpectation — 是用于测试异步代码的机制。测试创建一个具有描述性名称的期望,将其传递给异步操作,并调用 `wait(for:timeout:)`。如果在超时时间内未满足期望,则测试失败。超时默认是 10 秒,但对于快速操作,建议设置为 1-3 秒,以加快总测试时间。

XCTWaiter — 是 wait(for:timeout:) 的更灵活的替代方案。XCTWaiter 允许等待多个期望、配置执行顺序并以编程方式处理超时。与 wait 不同,XCTWaiter 返回 `XCTWaiter.Result`,可以对其进行分析。委托 XCTWaiterDelegate 通知期望顺序的违反和超时。

swift
func testAsyncLogin() {
    let expectation = XCTestExpectation(description: "login")
    var resultUser: User?

    sut.login(email: "a@b.com", password: "123") { user in
        resultUser = user
        expectation.fulfill()
    }

    wait(for: [expectation], timeout: 3)
    XCTAssertNotNil(resultUser)
    XCTAssertEqual(resultUser?.email, "a@b.com")
}

在示例中,XCTestExpectation 用于测试异步登录。fulfill() 在回调内部调用,表示异步操作已完成。如果在 3 秒内没有调用 fulfill(),则测试因超时而失败。成功等待后,执行断言以检查结果。多个期望可以作为数组传递,并等待所有期望满足。

使用 XCTest 进行性能测试

measure(metrics:) — XCTestCase 用于创建性能测试的方法。measure 内部的代码块连续执行 10 次,XCTest 收集统计数据:平均时间、中位数、标准差。Metrics — 被跟踪的度量数组:XCTClockMetric(时间)、XCTMemoryMetric(内存)、XCTStorageMetric(磁盘)和 XCTCPUMetric(处理器)。根据 Apple WWDC(2024),使用 XCTCPUMetric 的性能测试有助于检测算法中的回归。

Baseline(基线)用于性能测试,在 Xcode Test Plan 中设置。如果执行时间超过基线规定百分比(默认为 10%),则测试被认为是失败的。在确认性能变化是预期之后,基线会手动更新。Test Plan 在 Xcode 中允许按配置对性能测试进行分组:debug/release、不同设备、不同 iOS 版本。

swift
func testArraySortPerformance() {
    let numbers = (1...10000).shuffled()
    measure(metrics: [XCTClockMetric()]) {
        let _ = numbers.sorted()
    }
}

此性能测试测量对 10000 个元素的数组进行排序的时间。XCTClockMetric 记录实际执行时间。如果更改排序算法后时间增加 10% 或更多,测试将指示回归。XCTest 性能测试特别适用于:数据处理算法、UI 组件渲染、数据库操作和网络请求。

测试组织和 CI/CD 集成

测试项目的结构

测试项目的结构在 XCTest 中遵循约定:每个类一个测试文件,放在单独的 `<TargetName>Tests` 目录中。文件名与被测试的类名相对应,后缀为 `Tests`:`UserService.swift` → `UserServiceTests.swift`。Xcode 中的 Test Targets 为单元测试和 UI 测试分别配置,允许它们独立运行。Schemes 在 Xcode 中管理构建配置和要运行的测试集。

CI/CD 集成

Xcode Cloud 和 GitHub Actions 支持通过 `xcodebuild test -scheme App -testPlan SmokeTest` 运行 XCTest。CI 流水线包括:构建 → 运行单元测试 → 运行 UI 测试 → 发布报告。JUnit 报告由 `xcodebuild` 使用 `-resultBundlePath` 选项生成,可以导入到任何 CI 工具中。Code Coverage — XCTest 的内置功能,显示哪些代码行被测试覆盖。生产代码的最低覆盖率阈值 — 关键业务逻辑为 70%。

Xcode Cloud 和 GitHub Actions 支持通过 `xcodebuild test -scheme App -testPlan SmokeTest` 运行 XCTest。CI 流水线包括:构建 → 运行单元测试 → 运行 UI 测试 → 发布报告。JUnit 报告由 `xcodebuild` 使用 `-resultBundlePath` 选项生成,可以导入到任何 CI 工具中。Bitrise 和 Jenkins 有现成的 XCTest 步骤。

Code Coverage — XCTest 的内置功能,显示哪些代码行被测试覆盖。Xcode 以绿色(已覆盖)、红色(未覆盖)和黄色(部分覆盖)显示覆盖率。最低阈值生产代码的覆盖率 — 关键业务逻辑为 70%。根据 Google Testing Blog(2024),对所有模块强制要求 80% 的覆盖率会导致出现"空测试",这些测试不检查逻辑,只执行代码。

常见问题

XCTest 与 Quick 和 Nimble 有何不同?

XCTest — Apple 的官方框架,直接集成在 Xcode 中。Quick 和 Nimble — 第三方库,提供 BDD 语法和更易读的断言。Quick 和 Nimble 对于验收测试很方便,但 XCTest 由于没有外部依赖,对于单元测试更可靠。

如何在 XCTest 中测试异步代码?

异步代码通过 XCTestExpectation + `wait(for:timeout:)` 或通过 XCTest 的 `async/await` 方法(iOS 13+)进行测试。对于基于回调的 API,创建一个在闭包中调用的期望。对于 async/await,在异步函数中使用标准断言。

如何在 XCTest 中模拟依赖?

XCTest 没有内置的模拟框架。模拟通过协议实现:创建一个模拟类,实现与真实依赖相同的协议。对于自动生成模拟,使用 Cuckoo、SwiftyMocky 或手动模拟。通过初始化器进行依赖注入 — 测试的必要条件。

XCTest 可以在真实设备上运行吗?

是的,XCTest 通过 Xcode 或带有参数 `-destination 'platform=iOS,name=iPhone 15'` 的 xcodebuild 在真实设备上运行。UI 测试在真实设备上比在模拟器上提供更准确的结果。在设备农场上的运行使用 BrowserStack、Sauce Labs 或 Firebase Test Lab。

Swift Testing 相比 XCTest 有什么新功能?

Swift Testing(2024)— Apple 的新框架,带有 `@Test`、`@Suite` 和 `@Expect` 宏。它提供内置的测试参数化、在套件中分组以及更易读的语法。Swift Testing 与 XCTest 共存,并不取代它。XCTest 仍然是 UI 测试和性能测试的主要框架。

总结

  • XCTest — Apple 的官方测试框架,内置于 Xcode 并支持 Swift 和 Objective-C。
  • XCTestCase 提供 setUp/tearDown 生命周期和一组用于单元测试的断言。
  • XCTestExpectation 和 XCTWaiter — 使用回调和 async/await 测试异步代码的机制。
  • 性能测试 使用 measure(metrics:),支持 XCTClockMetric、XCTMemoryMetric 和 XCTCPUMetric。
  • 断言 — XCTAssertEqual、XCTAssertTrue、XCTAssertNil、XCTAssertThrowsError、XCTUnwrap 等。
  • CI/CD 集成 通过 xcodebuild、Xcode Cloud 和 GitHub Actions 自动生成报告。
  • Swift Testing — Apple 的新框架,补充 XCTest 用于单元测试和参数化。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读