swift-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Swift Testing

Swift Testing

Overview

概述

This skill provides expert guidance on Swift Testing, covering the modern Swift Testing framework, test doubles (mocks, stubs, spies), fixtures, integration testing, snapshot testing, and migration from XCTest. Use this skill to help developers write reliable, maintainable tests following F.I.R.S.T. principles and Arrange-Act-Assert patterns.
本技能提供关于Swift Testing的专业指导,涵盖现代Swift Testing框架、测试替身(模拟对象、存根、间谍对象)、测试固件、集成测试、快照测试以及从XCTest的迁移。使用本技能可帮助开发者遵循F.I.R.S.T.原则与Arrange-Act-Assert模式,编写可靠、可维护的测试。

Agent Behavior Contract (Follow These Rules)

Agent行为约定(请遵循以下规则)

  1. Use Swift Testing framework (
    @Test
    ,
    #expect
    ,
    #require
    ,
    @Suite
    ) for all new tests, not XCTest.
  2. Always structure tests with clear Arrange-Act-Assert phases.
  3. Follow F.I.R.S.T. principles: Fast, Isolated, Repeatable, Self-Validating, Timely.
  4. Use proper test double terminology per Martin Fowler's taxonomy (Dummy, Fake, Stub, Spy, SpyingStub, Mock).
  5. Place fixtures close to models with
    #if DEBUG
    , not in test targets.
  6. Place test doubles close to interfaces with
    #if DEBUG
    , not in test targets.
  7. Prefer state verification over behavior verification - simpler, less brittle tests.
  8. Use
    #expect
    for soft assertions (continue on failure) and
    #require
    for hard assertions (stop on failure).
  1. 所有新测试均使用Swift Testing框架(
    @Test
    #expect
    #require
    @Suite
    ),而非XCTest。
  2. 始终以清晰的Arrange-Act-Assert阶段构建测试。
  3. 遵循F.I.R.S.T.原则:Fast(快速)、Isolated(独立)、Repeatable(可重复)、Self-Validating(自验证)、Timely(及时)。
  4. 采用Martin Fowler分类法中的标准测试替身术语(Dummy、Fake、Stub、Spy、SpyingStub、Mock)。
  5. 测试固件需通过
    #if DEBUG
    标记放置在模型附近,而非测试目标中。
  6. 测试替身需通过
    #if DEBUG
    标记放置在接口附近,而非测试目标中。
  7. 优先使用状态验证而非行为验证——测试更简单、更不易失效。
  8. 使用
    #expect
    进行软断言(失败后继续执行测试),使用
    #require
    进行硬断言(失败后终止测试)。

Quick Decision Tree

快速决策树

When a developer needs testing guidance, follow this decision tree:
  1. Starting fresh with Swift Testing?
    • Read
      references/test-organization.md
      for suites, tags, traits
    • Read
      references/async-testing.md
      for async test patterns
  2. Need to create test data?
    • Read
      references/fixtures.md
      for fixture patterns and placement
    • Read
      references/test-doubles.md
      for mock/stub/spy patterns
  3. Testing multiple inputs?
    • Read
      references/parameterized-tests.md
      for parameterized testing
  4. Testing module interactions?
    • Read
      references/integration-testing.md
      for integration test patterns
  5. Testing UI for regressions?
    • Read
      references/snapshot-testing.md
      for snapshot testing setup
  6. Testing data structures or state?
    • Read
      references/dump-snapshot-testing.md
      for text-based snapshot testing
  7. Migrating from XCTest?
    • Read
      references/migration-xctest.md
      for migration guide
当开发者需要测试指导时,请遵循以下决策树:
  1. 刚开始使用Swift Testing?
    • 阅读
      references/test-organization.md
      了解测试套件、标签、特性
    • 阅读
      references/async-testing.md
      了解异步测试模式
  2. 需要创建测试数据?
    • 阅读
      references/fixtures.md
      了解测试固件模式与放置位置
    • 阅读
      references/test-doubles.md
      了解模拟对象/存根/间谍对象模式
  3. 需要测试多组输入?
    • 阅读
      references/parameterized-tests.md
      了解参数化测试
  4. 需要测试模块交互?
    • 阅读
      references/integration-testing.md
      了解集成测试模式
  5. 需要测试UI回归问题?
    • 阅读
      references/snapshot-testing.md
      了解快照测试设置
  6. 需要测试数据结构或状态?
    • 阅读
      references/dump-snapshot-testing.md
      了解基于文本的快照测试
  7. 从XCTest迁移?
    • 阅读
      references/migration-xctest.md
      了解迁移指南

Triage-First Playbook (Common Errors -> Next Best Move)

优先排查手册(常见错误 -> 最佳解决方案)

  • "XCTAssertEqual is unavailable" / need to modernize tests
    • Use
      references/migration-xctest.md
      for XCTest to Swift Testing migration
  • Need to test async code
    • Use
      references/async-testing.md
      for async patterns, confirmation, timeouts
  • Tests are slow or flaky
    • Check F.I.R.S.T. principles, use proper mocking per
      references/test-doubles.md
  • Need deterministic test data
    • Use
      references/fixtures.md
      for fixture patterns with fixed dates
  • Need to test multiple scenarios efficiently
    • Use
      references/parameterized-tests.md
      for parameterized testing
  • Need to verify component interactions
    • Use
      references/integration-testing.md
      for integration test patterns
  • "XCTAssertEqual不可用" / 需要现代化测试
    • 使用
      references/migration-xctest.md
      进行XCTest到Swift Testing的迁移
  • 需要测试异步代码
    • 使用
      references/async-testing.md
      了解异步模式、确认机制、超时处理
  • 测试运行缓慢或不稳定
    • 检查是否遵循F.I.R.S.T.原则,按照
      references/test-doubles.md
      使用正确的模拟方式
  • 需要确定性测试数据
    • 使用
      references/fixtures.md
      中的固定日期测试固件模式
  • 需要高效测试多种场景
    • 使用
      references/parameterized-tests.md
      进行参数化测试
  • 需要验证组件交互
    • 使用
      references/integration-testing.md
      中的集成测试模式

Core Syntax

核心语法

Basic Test

基础测试

swift
import Testing

@Test func basicTest() {
    #expect(1 + 1 == 2)
}
swift
import Testing

@Test func basicTest() {
    #expect(1 + 1 == 2)
}

Test with Description

带描述的测试

swift
@Test("Adding items increases cart count")
func addItem() {
    let cart = Cart()
    cart.add(item)
    #expect(cart.count == 1)
}
swift
@Test("添加商品会增加购物车数量")
func addItem() {
    let cart = Cart()
    cart.add(item)
    #expect(cart.count == 1)
}

Async Test

异步测试

swift
@Test func asyncOperation() async throws {
    let result = try await service.fetch()
    #expect(result.isValid)
}
swift
@Test func asyncOperation() async throws {
    let result = try await service.fetch()
    #expect(result.isValid)
}

Arrange-Act-Assert Pattern

Arrange-Act-Assert模式

Structure every test with clear phases:
swift
@Test func calculateTotal() {
    // Given
    let cart = ShoppingCart()
    cart.add(Item(price: 10))
    cart.add(Item(price: 20))

    // When
    let total = cart.calculateTotal()

    // Then
    #expect(total == 30)
}
所有测试均需按清晰的阶段构建:
swift
@Test func calculateTotal() {
    // Given(准备)
    let cart = ShoppingCart()
    cart.add(Item(price: 10))
    cart.add(Item(price: 20))

    // When(执行)
    let total = cart.calculateTotal()

    // Then(断言)
    #expect(total == 30)
}

Assertions

断言

#expect - Soft Assertion

#expect - 软断言

Continues test execution after failure:
swift
@Test func multipleExpectations() {
    let user = User(name: "Alice", age: 30)
    #expect(user.name == "Alice")  // If fails, test continues
    #expect(user.age == 30)        // This still runs
}
失败后继续执行测试:
swift
@Test func multipleExpectations() {
    let user = User(name: "Alice", age: 30)
    #expect(user.name == "Alice")  // 若失败,测试继续执行
    #expect(user.age == 30)        // 此断言仍会运行
}

#require - Hard Assertion

#require - 硬断言

Stops test execution on failure:
swift
@Test func requireExample() throws {
    let user = try #require(fetchUser())  // Stops if nil
    #expect(user.name == "Alice")
}
失败后终止测试执行:
swift
@Test func requireExample() throws {
    let user = try #require(fetchUser())  // 若为nil则终止测试
    #expect(user.name == "Alice")
}

Error Testing

错误测试

swift
@Test func throwsError() {
    #expect(throws: ValidationError.self) {
        try validate(invalidInput)
    }
}

@Test func throwsSpecificError() {
    #expect(throws: ValidationError.emptyField) {
        try validate("")
    }
}
swift
@Test func throwsError() {
    #expect(throws: ValidationError.self) {
        try validate(invalidInput)
    }
}

@Test func throwsSpecificError() {
    #expect(throws: ValidationError.emptyField) {
        try validate("")
    }
}

F.I.R.S.T. Principles

F.I.R.S.T.原则

PrincipleDescriptionApplication
FastTests execute in millisecondsMock expensive operations
IsolatedTests don't depend on each otherFresh instance per test
RepeatableSame result every timeMock dates, network, external deps
Self-ValidatingAuto-report pass/failUse
#expect
, never rely on
print()
TimelyWrite tests alongside codeUse parameterized tests for edge cases
Principle描述应用
Fast测试以毫秒级运行模拟昂贵(耗时/资源密集)的操作
Isolated测试之间互不依赖每个测试使用全新实例
Repeatable每次运行结果一致模拟日期、网络、外部依赖
Self-Validating自动报告通过/失败使用
#expect
,绝不依赖
print()
Timely与代码同步编写使用参数化测试覆盖边缘案例

Test Double Quick Reference

测试速查

TypePurposeVerification
DummyFill parameters, never usedN/A
FakeWorking implementation with shortcutsState
StubProvides canned answersState
SpyRecords calls for verificationState
SpyingStubStub + Spy combined (most common)State
MockPre-programmed expectations, self-verifiesBehavior
Important: What Swift community calls "Mock" is usually a SpyingStub.
For detailed patterns, see
references/test-doubles.md
.
类型用途验证方式
Dummy填充参数,从未被使用
Fake带简化实现的可用版本状态
Stub提供预设响应状态
Spy记录调用情况用于验证状态
SpyingStub存根+间谍的组合(最常用)状态
Mock预定义期望并自行验证行为
重要提示:Swift社区中所谓的"Mock"通常指SpyingStub
如需详细模式,请查看
references/test-doubles.md

Test Double Placement

测试替身放置

Place test doubles close to the interface, not in test targets:
swift
// In PersonalRecordsCore-Interface/Sources/...

public protocol PersonalRecordsRepositoryProtocol: Sendable {
    func getAll() async throws -> [PersonalRecord]
    func save(_ record: PersonalRecord) async throws
}

#if DEBUG
public final class PersonalRecordsRepositorySpyingStub: PersonalRecordsRepositoryProtocol {
    // Spy: Captured calls
    public private(set) var savedRecords: [PersonalRecord] = []

    // Stub: Configurable responses
    public var recordsToReturn: [PersonalRecord] = []
    public var errorToThrow: Error?

    public func getAll() async throws -> [PersonalRecord] {
        if let error = errorToThrow { throw error }
        return recordsToReturn
    }

    public func save(_ record: PersonalRecord) async throws {
        if let error = errorToThrow { throw error }
        savedRecords.append(record)
    }
}
#endif
测试替身需放置在接口附近,而非测试目标中:
swift
// In PersonalRecordsCore-Interface/Sources/...

public protocol PersonalRecordsRepositoryProtocol: Sendable {
    func getAll() async throws -> [PersonalRecord]
    func save(_ record: PersonalRecord) async throws
}

#if DEBUG
public final class PersonalRecordsRepositorySpyingStub: PersonalRecordsRepositoryProtocol {
    // Spy: 捕获的调用记录
    public private(set) var savedRecords: [PersonalRecord] = []

    // Stub: 可配置的响应
    public var recordsToReturn: [PersonalRecord] = []
    public var errorToThrow: Error?

    public func getAll() async throws -> [PersonalRecord] {
        if let error = errorToThrow { throw error }
        return recordsToReturn
    }

    public func save(_ record: PersonalRecord) async throws {
        if let error = errorToThrow { throw error }
        savedRecords.append(record)
    }
}
#endif

Fixtures

测试固件

Place fixtures close to the model:
swift
// In Sources/Models/PersonalRecord.swift

public struct PersonalRecord: Equatable, Sendable {
    public let id: UUID
    public let weight: Double
    // ...
}

#if DEBUG
extension PersonalRecord {
    public static func fixture(
        id: UUID = UUID(),
        weight: Double = 100.0
        // ... defaults for all properties
    ) -> PersonalRecord {
        PersonalRecord(id: id, weight: weight)
    }
}
#endif
For detailed patterns, see
references/fixtures.md
.
测试固件需放置在模型附近
swift
// In Sources/Models/PersonalRecord.swift

public struct PersonalRecord: Equatable, Sendable {
    public let id: UUID
    public let weight: Double
    // ...
}

#if DEBUG
extension PersonalRecord {
    public static func fixture(
        id: UUID = UUID(),
        weight: Double = 100.0
        // ... 所有属性的默认值
    ) -> PersonalRecord {
        PersonalRecord(id: id, weight: weight)
    }
}
#endif
如需详细模式,请查看
references/fixtures.md

Test Pyramid

测试金字塔

        +-------------+
        |   UI Tests  |  5%  - End-to-end flows
        |   (E2E)     |
        +-------------+
        | Integration |  15% - Module interactions
        |    Tests    |
        +-------------+
        |    Unit     |  80% - Individual components
        |    Tests    |
        +-------------+
        +-------------+
        |   UI测试    |  5%  - 端到端流程
        |   (E2E)     |
        +-------------+
        | 集成测试    |  15% - 模块交互
        |             |
        +-------------+
        | 单元测试    |  80% - 独立组件
        |             |
        +-------------+

Reference Files

参考文件

Load these files as needed for specific topics:
  • test-organization.md
    - Suites, tags, traits, parallel execution
  • parameterized-tests.md
    - Testing multiple inputs efficiently
  • async-testing.md
    - Async patterns, confirmation, timeouts, cancellation
  • migration-xctest.md
    - Complete XCTest to Swift Testing migration guide
  • test-doubles.md
    - Complete taxonomy with examples (Dummy, Fake, Stub, Spy, SpyingStub, Mock)
  • fixtures.md
    - Fixture patterns, placement, and best practices
  • integration-testing.md
    - Module interaction testing patterns
  • snapshot-testing.md
    - UI regression testing with SnapshotTesting library
  • dump-snapshot-testing.md
    - Text-based snapshot testing for data structures
根据特定主题需求加载以下文件:
  • test-organization.md
    - 测试套件、标签、特性、并行执行
  • parameterized-tests.md
    - 高效测试多组输入
  • async-testing.md
    - 异步模式、确认机制、超时处理、取消操作
  • migration-xctest.md
    - XCTest到Swift Testing的完整迁移指南
  • test-doubles.md
    - 含示例的完整分类(Dummy、Fake、Stub、Spy、SpyingStub、Mock)
  • fixtures.md
    - 测试固件模式、放置位置与最佳实践
  • integration-testing.md
    - 模块交互测试模式
  • snapshot-testing.md
    - 使用SnapshotTesting库进行UI回归测试
  • dump-snapshot-testing.md
    - 针对数据结构的基于文本的快照测试

Best Practices Summary

最佳实践总结

  1. Use Swift Testing for new tests - Modern syntax, better features
  2. Follow Arrange-Act-Assert - Clear test structure
  3. Apply F.I.R.S.T. principles - Fast, Isolated, Repeatable, Self-Validating, Timely
  4. Place fixtures near models - With
    #if DEBUG
    guards
  5. Place test doubles near interfaces - With
    #if DEBUG
    guards
  6. Prefer state verification - Simpler, less brittle than behavior verification
  7. Use parameterized tests - For testing multiple inputs efficiently
  8. Follow test pyramid - 80% unit, 15% integration, 5% UI
  1. 新测试使用Swift Testing - 现代语法,更优特性
  2. 遵循Arrange-Act-Assert - 清晰的测试结构
  3. 应用F.I.R.S.T.原则 - 快速、独立、可重复、自验证、及时
  4. 测试固件靠近模型放置 - 使用
    #if DEBUG
    保护
  5. 测试替身靠近接口放置 - 使用
    #if DEBUG
    保护
  6. 优先使用状态验证 - 比行为验证更简单、更不易失效
  7. 使用参数化测试 - 高效测试多组输入
  8. 遵循测试金字塔 - 80%单元测试、15%集成测试、5%UI测试

Verification Checklist (When You Write Tests)

验证清单(编写测试时)

  • Tests follow Arrange-Act-Assert pattern
  • Test names describe behavior, not implementation
  • Fixtures use sensible defaults, not random values
  • Test doubles are minimal (only stub what's needed)
  • Async tests use proper patterns (async/await, confirmation)
  • Tests are fast (mock expensive operations)
  • Tests are isolated (no shared state)
  • Tests are repeatable (no flaky date/time dependencies)
  • 测试遵循Arrange-Act-Assert模式
  • 测试名称描述行为而非实现
  • 测试固件使用合理默认值,而非随机值
  • 测试替身保持最小化(仅存根必要内容)
  • 异步测试使用正确模式(async/await、确认机制)
  • 测试运行快速(模拟耗时操作)
  • 测试相互独立(无共享状态)
  • 测试结果可重复(无依赖日期/时间的不稳定因素)