askill
swift-testing

swift-testingSafety 85Repository

Swift Testing框架。使用@Test、#expect、#require编写测试、迁移XCTest、实现async测试、参数化测试时。

0 stars
1.2k downloads
Updated 1/29/2026

Package Files

Loading files...
SKILL.md

Swift Testing Framework

Swift Testing是Apple的现代测试框架,使用宏和结构化并发替代XCTest。核心原则:如果学了XCTest,请忘记它——Swift Testing工作方式不同。

Overview

import Testing
@testable import YourApp

@Suite
struct FeatureTests {
    @Test("Feature performs correctly")
    func testFeature() {
        let result = performFeature()
        #expect(result == expected)
    }
}

Core Concepts

Assertions

MacroUse CaseDescription
#expect(expression)大多数断言软检查 - 失败继续
#require(expression)前置条件硬检查 - 失败停止

基本结构

import Testing
@testable import YourApp

@Suite
struct MyFeatureTests {
    let sut: MyFeature

    init() throws {
        // Setup before each test
        sut = MyFeature()
    }

    @Test("Example test description")
    func exampleBehavior() {
        // Arrange
        let input = "test"

        // Act
        let result = sut.process(input)

        // Assert
        #expect(result == "expected")
    }
}

Test Types

基本测试

@Test("Addition works correctly")
func testAddition() {
    #expect(2 + 2 == 4)
}

异步测试

@Test("Async data fetch")
func testAsyncFetch() async throws {
    let data = try await fetchData()
    #expect(!data.isEmpty)
}

Optional展开

@Test("Unwrap optional")
func testUnwrap() async throws {
    let user = try #require(await fetchUser())
    #expect(user.id == "123")
}

参数化测试

@Test("Validation works", arguments: [
    ("valid@email.com", true),
    ("invalid-email", false),
    ("", false)
])
func testValidation(email: String, expected: Bool) {
    #expect(Validator.isValid(email) == expected)
}

多参数测试(使用zip配对)

@Test("Pair processing", arguments: zip(
    ["a", "b", "c"],
    [1, 2, 3]
))
func testPairs(input: String, expected: Int) {
    #expect(process(input) == expected)
}

错误测试

@Test("Throws correct error")
func testThrows() {
    #expect(throws: NetworkError.self) {
        try riskyOperation()
    }
}

@Test("Throws specific error")
func testThrowsSpecific() {
    #expect(throws: NetworkError.timeout) {
        try fetch()
    }
}

@Test("Does not throw")
func testNoThrow() {
    #expect(throws: Never.self) {
        try safeOperation()
    }
}

XCTest迁移

断言转换

XCTestSwift Testing
XCTAssertTrue(expr)#expect(expr == true)
XCTAssertFalse(expr)#expect(expr == false)
XCTAssertEqual(a, b)#expect(a == b)
XCTAssertNotEqual(a, b)#expect(a != b)
XCTAssertNil(a)#expect(a == nil)
XCTAssertNotNil(a)#expect(a != nil)
XCTAssertGreaterThan(a, b)#expect(a > b)
XCTAssertThrowsError#expect(throws: Error.self) { }
XCTAssertNoThrow#expect(throws: Never.self) { }
try XCTUnwrap(a)try #require(a)

Setup/Teardown

// XCTest
override func setUp() { }
override func tearDown() { }

// Swift Testing (使用init/deinit或单独的测试)
@Suite
struct MyTests {
    var sut: SystemUnderTest

    init() {
        // Setup
        sut = SystemUnderTest()
    }

    deinit {
        // Teardown
        sut.cleanup()
    }
}

高级特性

Tags

extension Tag {
    @Tag static var fast: Tag
    @Tag static var network: Tag
}

@Test(.tags(.fast, .network))
func testNetworkCall() { }

条件测试

@Test("iOS 18+ feature", .enabled(if: #available(iOS 18.0, *)))
func testNewFeature() { }

@Test("Debug only", .enabled(if: _isDebugAssertConfiguration()))
func testDebugBehavior() { }

Confirmation(回调验证)

@Test("Callback invoked")
func testCallback() async {
    await confirmation("Callback received") { confirm in
        let object = TestObject { confirm() }
        object.triggerCallback()
    }
}

禁用序列化(测试线程不安全代码)

@Suite(.serialized)
struct LegacyTests {
    // Tests run sequentially, not in parallel
}

最佳实践

1. 状态隔离

@Suite
struct CounterTests {
    // 每个测试获得新实例
    let counter: Counter

    init() {
        counter = Counter()
    }

    @Test("Increment works")
    func testIncrement() {
        counter.increment()
        #expect(counter.value == 1)
    }
}

2. 可描述的测试名称

// ❌ 差
@Test("test1")
func test1() { }

// ✅ 好
@Test("User login fails with invalid credentials")
func testLoginFailsWithInvalidCredentials() { }

3. 测试行为,而非实现

// ❌ 测试实现细节
@Test("Counter calls increment method")

// ✅ 测试行为
@Test("Counter increases value when increment called")

4. 使用辅助函数

@Suite
struct HelperTests {
    func makeSUT() -> MyViewModel {
        MyViewModel(service: MockService())
    }

    @Test("View model initializes")
    func testInit() {
        let sut = makeSUT()
        #expect(sut.isLoaded == false)
    }
}

Common Mistakes

  1. 过度使用#require

    • ❌ 对大多数断言使用 #require
    • ✅ 大多数使用 #expect#require 仅用于前置条件
  2. 笛卡尔积错误

    • @Test(arguments: [a, b], [c, d]) - 创建4种组合
    • @Test(arguments: zip([a, b], [c, d])) - 配对参数
  3. 忽略状态隔离

    • Swift Testing为每个测试创建新实例
    • 但静态变量和单例仍然会泄漏状态
    • 使用依赖注入清理单例
  4. 并行测试冲突

    • 默认并行运行测试
    • 共享文件/数据库/单例会冲突
    • 使用 .serialized 或隔离策略
  5. 不自然使用async

    • Task { await asyncOperation() }
    • ✅ 直接在测试签名使用:@Test func testAsync() async throws { }
  6. Confirmation误用

    • confirmation 是验证回调被调用
    • 对断言使用是错误的
    • 断言用 #expect

iOS测试特殊考虑

@MainActor测试

@Suite
@MainActor
struct ViewModelTests {
    @Test("View model updates on main thread")
    func testUpdate() {
        let viewModel = MyViewModel()
        viewModel.update()
        #expect(viewModel.isUpdated == true)
    }
}

UI测试(XCTest仍需)

import XCTest

final class MyUITests: XCTestCase {
    func testLoginFlow() {
        let app = XCUIApplication()
        app.launch()

        app.textFields["emailField"].tap()
        app.textFields["emailField"].typeText("test@example.com")

        app.buttons["loginButton"].tap()

        XCTAssertTrue(app.staticTexts["welcomeLabel"].exists)
    }
}

测试覆盖率目标

类型目标覆盖率
关键业务逻辑80%+
标准功能60%+
UI组件关注行为,非渲染细节

输出格式示例

import Testing
@testable import MyApp

@Suite("UserAuthentication")
@MainActor
struct AuthenticationTests {

    @Test("Login succeeds with valid credentials", .tags(.network))
    func testSuccessfulLogin() async throws {
        // Given
        let viewModel = LoginViewModel(service: MockAPIService())
        let credentials = Credentials(email: "test@example.com", password: "password")

        // When
        try await viewModel.login(credentials)

        // Then
        #expect(viewModel.isLoggedIn == true)
        #expect(viewModel.currentUser?.email == "test@example.com")
    }

    @Test("Login fails with invalid credentials")
    func testFailedLogin() async throws {
        let viewModel = LoginViewModel(service: FailingMockAPIService())
        let credentials = Credentials(email: "invalid", password: "wrong")

        await confirmation("Error callback invoked") { confirm in
            viewModel.onError = { _ in confirm() }
            try? await viewModel.login(credentials)
        }
    }

    @Test("Password validation rejects short passwords", arguments: [
        ("", false),
        ("ab", false),
        ("abc", false),
        ("ValidPass123", true)
    ])
    func testPasswordValidation(password: String, expected: Bool) {
        #expect(Validator.isValidPassword(password) == expected)
    }
}

Quick Reference

需要代码
软断言#expect(a == b)
硬断言try #require(a)
异步测试func test() async throws { }
参数化@Test(arguments: [a, b, c])
错误测试#expect(throws: Error.self) { }
回调验证await confirmation("name") { confirm in }
条件运行.enabled(if: condition)

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

94/100Analyzed 2/9/2026

An exceptional technical reference for the Swift Testing framework, providing comprehensive coverage from basic assertions to advanced concurrency and migration from XCTest.

85
95
90
95
95

Metadata

Licenseunknown
Version-
Updated1/29/2026
Publisherliujinwakayakulie

Tags

testing