mcpField
S
swift-testing

swift-testing

github.com/dpearson2699テスト・品質1.2k4.4k 回インストール13 週間前に更新
リポジトリ

swift-testing について

Swift Testingを使うSwift開発者向けのモダンなユニットテストガイドです。テスト宣言、アサーション、並列実行、XCTestとの移行境界、応用APIのバージョン要件を説明します。

主な機能

  1. 01@Testによるテスト宣言
  2. 02#expectと#requireによるアサーション
  3. 03スイート、タグ、テスト特性
  4. 04並列実行と状態の分離
  5. 05XCTestとの併用と移行
  6. 06バージョン要件付きAPIの指針

活用シーン

  1. 01Swiftコードの振る舞いをテスト
  2. 02非同期処理とエラーパスをテスト
  3. 03共有外部状態にアクセスするテストを分離
  4. 04XCTestのユニットテストをスイート単位で移行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-testing

インストール後、Claude Code で/swift-testingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
testing-advanced.md
testing-patterns.md

詳細

Swift Testing

Swift Testing は、Swift 向けの最新のテストフレームワークです(Xcode 16 以降、Swift 6 以降)。新しい単体テストには、こちらを優先して使用してください。移行がまだ進行中の場合は XCTest を維持し、UI 自動化、パフォーマンス API、Objective-C の例外テスト、一般的なスナップショットテストツールには XCTest を使用してください。

目次


基本的なテスト

import Testing

@Test("User can update their display name")
func updateDisplayName() {
    var user = User(name: "Alice")
    user.name = "Bob"
    #expect(user.name == "Bob")
}

@Test の特性

@Test("Validates email format")                                    // display name
@Test(.tags(.validation, .email))                                  // tags
@Test(.disabled("Server migration in progress"))                   // disabled
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil)) // conditional
@Test(.bug("https://github.com/org/repo/issues/42"))               // bug reference
@Test(.timeLimit(.minutes(1)))                                     // time limit
@Test("Timeout handling", .tags(.networking), .timeLimit(.seconds(30))) // combined

#expect と #require

// #expect records failure but continues execution
#expect(result == 42)
#expect(name.isEmpty == false)
#expect(items.count > 0, "Items should not be empty")

// #expect with error type checking
#expect(throws: ValidationError.self) {
    try validate(email: "not-an-email")
}

// #expect with specific error value
#expect {
    try validate(email: "")
} throws: { error in
    guard let err = error as? ValidationError else { return false }
    return err == .empty
}

// #require records failure AND stops test (like XCTUnwrap)
let user = try #require(await fetchUser(id: 1))
#expect(user.name == "Alice")

// #require for optionals -- unwraps or fails
let first = try #require(items.first)
#expect(first.isValid)

ルール:後続のアサーションが値に依存する場合は #require を使用してください。独立したチェックには #expect を使用してください。

@Suite とテストの整理

スイートの整理、確認パターン、既知の問題への対処、実行モデルの詳細については、references/testing-patterns.md を参照してください。

実行モデル

Swift Testing では、テストはデフォルトで並列実行されます。明示的に設計しない限り、テストの実行順序、スイートインスタンスの共有、可変状態への排他的なアクセスを前提としないでください。

@Suite(.serialized)
struct KeychainTests {
    @Test func storesToken() throws { /* ... */ }
    @Test func deletesToken() throws { /* ... */ }
}

共有の外部状態にアクセスするため、テストまたはスイートを一度に 1 つずつ実行する必要がある場合は、.serialized を使用してください。この指定によって、そのスコープ外にある無関係なテストが逐次実行されることはありません。

ルール:

  • 各テストで、それぞれの状態をセットアップしてください。
  • 共有される可変グローバルは、保護されているか、意図的に逐次実行されているのでなければ、バグです。
  • @Suite(.serialized) は排他的な実行のための指定であり、テスト間の論理的な順序を表すものではありません。
  • テストが順序に依存する場合は、1 つのテストにまとめるか、順序を共有ヘルパーコードに移してください。

XCTest からの移行範囲

Swift Testing の単体テストは XCTestCase を継承しません。@Test は、自由関数、または struct、class、actor などのスイート型のメソッドに宣言します。インスタンスフィクスチャが不要な場合は、static または class メソッドを使用してください。

移行中は XCTest と Swift Testing を共存させることができます。ファイルまたはスイート単位で移行し、検出数、成功数、失敗数、スキップ数を比較してください。また、UI 自動化、パフォーマンスベンチマーク、一般的なスナップショットのワークフローには、引き続き XCTest/XCUITest またはスナップショットツールを使用してください。テストランナーに関する想定を明確にできる場合は、ファイルまたはターゲットを分けてください。

Xcode 27 世代の混在ヘルパーについては、クロスフレームワーク API が禁止されていると断定するのではなく、設定されている相互運用モードを確認してください。古いテストプランでは limited が継承され、新しいプロジェクトでは complete が使用されます。また、strict と none も選択できます。移行中は complete または strict を優先し、必要に応じて SwiftPM では SWIFT_TESTING_XCTEST_INTEROP_MODE を使用してください。モードの一覧とツールチェーンごとの利用条件については、references/testing-advanced.md を参照してください。

すべての XCTest アサーションを機械的に #expect に置き換えないでください。必要なアンラップと無条件の失敗は、次の移行時の既定の対応方法に従って維持してください。

  • XCTAssert* → #expect(...)
  • XCTUnwrap、または後続のチェックで必要となる値 → try #require(...)
  • XCTFail("...") または手動で記録する無条件の問題 → Issue.record("...")
  • UI テスト、パフォーマンスベンチマーク、一般的なスナップショットテストのワークフローには、引き続き XCTest/XCUITest またはスナップショットツールを使用してください。
  • @available はスイート型やその包含型ではなく、個々の @Test 関数に付けてください。

移行例については references/testing-patterns.md を、Swift/Xcode のバージョンごとの利用条件については references/testing-advanced.md を参照してください。

既知の問題

想定内の失敗をマークし、テストが失敗しないようにしてください。

withKnownIssue("Propane tank is empty") {
    #expect(truck.grill.isHeating)
}

// Intermittent / flaky failures
withKnownIssue(isIntermittent: true) {
    #expect(service.isReachable)
}

// Conditional known issue
withKnownIssue {
    #expect(foodTruck.grill.isHeating)
} when: {
    !hasPropane
}

既知の問題が記録されていない場合、Swift Testing は問題が解決している可能性を知らせる、個別の問題を記録します。

追加のパターン

パラメーター化テスト、タグとスイート、非同期テスト、トレイト、実行モデルの詳細については、references/testing-patterns.mdを参照してください。

テストへの添付

失敗のデバッグに役立つ診断データをテスト結果に添付します。詳しい例については、references/testing-patterns.mdを参照してください。

@Test func generateReport() async throws {
    let report = try generateReport()
    Attachment.record(report.data, named: "report.json")
    #expect(report.isValid)
}

画像の添付と、それに必要なツールチェーンの条件については、 バージョン要件付き APIの正規の表を参照してください。

終了テスト

サポート対象のランタイムでexit()、fatalError()、またはpreconditionFailure()を呼び出すコードをテストします。終了テストのコードを修正する際は、バージョン要件付き APIに記載されている正確な要件を示してください。

@Test func invalidInputCausesExit() async {
    await #expect(processExitsWith: .failure) {
        processInvalidInput()  // calls fatalError()
    }
}

バージョン要件付き API

高度な API を扱う場合は、修正内容の横にツールチェーンとランタイムの正確な要件を記載してください。以下が正規の概要です。詳しい表と例については、references/testing-advanced.mdを参照してください。

@Test func exitsWithCapturedCode() async {
    let expectedCode: Int32 = 42
    await #expect(processExitsWith: .failure) { [expectedCode] in
        exit(expectedCode)
    }
}
修正するユーザーコード現在のガイダンス
#expect(exitsWith:)await #expect(processExitsWith: .failure) { ... }を使用します。終了テストにはSwift 6.2/Xcode 26.0以降が必要で、macOS、Linux、FreeBSD、OpenBSD、Windowsのランタイムターゲットでサポートされています。iOS、tvOS、watchOSではサポートされません。iOSアプリのターゲットでは、終了しない小規模な API、またはサポート対象のホスト/ツールターゲットを通じて、致命的エラーの経路をテストしてください。
終了テストのクロージャーが外側の値を読み取る例のように、明示的なキャプチャーリストを追加します:{ [expectedCode] in ... }。終了テストのキャプチャーリストにはSwift 6.3コンパイラーが必要です。キャプチャーされた値はSendableかつCodableである必要があります。
処理をawaitするテスト内のTest.cancel()テストをasync throwsにして、try Test.cancel("reason")を呼び出します。Test.cancel(_:)にはSwift 6.3/Xcode 26.4相当のサポートが必要です。
Issue.record(..., severity: .warning)Issue.record("message", severity: .warning)を使用します。警告レベルは報告されますが、テストは失敗しません。Swift 6.3/Xcode 26.4相当のサポートが必要です。
Attachment(image, named:).record()Attachment.record(image, named: "name", as: .png)を使用します。Testingと、関連する画像フレームワークをインポートします。Appleプラットフォームの画像値には、UIImage、CGImage、CIImage、NSImageがあります。画像添付の記録にはSwift 6.3/Xcode 26.4相当のサポートが必要です。

よくある間違い

  1. 振る舞いではなく実装をテストする。 コードの仕組みではなく、コードが何をするかをテストします。
  2. エラー経路のテストがない。 関数がエラーをスローする可能性がある場合は、エラーをスローする経路をテストします。
  3. 不安定な非同期テストやスリープ。 スリープの代わりに、confirmation、クロックの注入、または並行処理プリミティブを使用します。
  4. テスト間で変更可能な状態を共有する。 各テストは、@Suite内のinit()を通じて独自の状態を設定します。
  5. UIテストにアクセシビリティ識別子がない。 XCUITestのクエリでは、これらの識別子が必要です。
  6. キャンセルをテストしない。 コードがTaskのキャンセルをサポートしている場合は、問題なくキャンセルされることを確認します。
  7. 移行の境界が不明確。 どちらかのフレームワークを全面的に選ぶのではなく、XCTestの移行境界に従ってください。
  8. Sendableでないヘルパーをテスト間で共有する。 共有ヘルパーをSendableにし、MainActorに依存するテストコードには@MainActorを付けます。
  9. シリアル実行を順序制御とみなす。 テストはデフォルトで並列実行されます。.serializedは排他的な状態を保護しますが、あるテストの結果を別のテストに渡すものではありません。

レビューチェックリスト

  • 追加するテストはすべて XCTest のアサーションではなく、Swift Testing(@Test、#expect)を使用しています
  • テスト名は動作を表しています(testFetchUser ではなく fetchUserReturnsNilOnNetworkError)
  • エラー処理のテストを個別に用意しています
  • 非同期テストでは Task.sleep ではなく confirmation() を使用しています
  • 繰り返しのバリエーションにはパラメーター化テストを使用しています
  • フィルタリング用にタグ(.critical、.slow)を適用しています
  • モックは具象型をサブクラス化せず、プロトコルに準拠しています
  • テスト間で共有の可変状態がありません
  • テストは宣言順序や共有スイートインスタンスに依存しません
  • .serialized は、ワークフローの順序をモデル化するためではなく、本当に排他的な状態にのみ使用しています
  • キャンセル可能な非同期操作のキャンセルをテストしています

参考資料

コメント