swift-testing について
Swift Testingを使うSwift開発者向けのモダンなユニットテストガイドです。テスト宣言、アサーション、並列実行、XCTestとの移行境界、応用APIのバージョン要件を説明します。
主な機能
- 01@Testによるテスト宣言
- 02#expectと#requireによるアサーション
- 03スイート、タグ、テスト特性
- 04並列実行と状態の分離
- 05XCTestとの併用と移行
- 06バージョン要件付きAPIの指針
活用シーン
- 01Swiftコードの振る舞いをテスト
- 02非同期処理とエラーパスをテスト
- 03共有外部状態にアクセスするテストを分離
- 04XCTestのユニットテストをスイート単位で移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swift-testingインストール後、Claude Code で/swift-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/testing-advanced.mdtesting-patterns.md詳細
Swift Testing
Swift Testing は、Swift 向けの最新のテストフレームワークです(Xcode 16 以降、Swift 6 以降)。新しい単体テストには、こちらを優先して使用してください。移行がまだ進行中の場合は XCTest を維持し、UI 自動化、パフォーマンス API、Objective-C の例外テスト、一般的なスナップショットテストツールには XCTest を使用してください。
目次
- 基本的なテスト
@Test Traits- #expect と #require
@Suite とテストの整理- 実行モデル
- XCTest からの移行範囲
- 既知の問題
- 追加のパターン
- よくある間違い
- テストの添付ファイル
- 終了のテスト
- バージョンによって利用可否が異なる API
- レビュー用チェックリスト
- 参考資料
基本的なテスト
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相当のサポートが必要です。 |
よくある間違い
- 振る舞いではなく実装をテストする。 コードの仕組みではなく、コードが何をするかをテストします。
- エラー経路のテストがない。 関数がエラーをスローする可能性がある場合は、エラーをスローする経路をテストします。
- 不安定な非同期テストやスリープ。 スリープの代わりに、
confirmation、クロックの注入、または並行処理プリミティブを使用します。 - テスト間で変更可能な状態を共有する。 各テストは、
@Suite内のinit()を通じて独自の状態を設定します。 - UIテストにアクセシビリティ識別子がない。 XCUITestのクエリでは、これらの識別子が必要です。
- キャンセルをテストしない。 コードが
Taskのキャンセルをサポートしている場合は、問題なくキャンセルされることを確認します。 - 移行の境界が不明確。 どちらかのフレームワークを全面的に選ぶのではなく、XCTestの移行境界に従ってください。
Sendableでないヘルパーをテスト間で共有する。 共有ヘルパーをSendableにし、MainActorに依存するテストコードには@MainActorを付けます。- シリアル実行を順序制御とみなす。 テストはデフォルトで並列実行されます。
.serializedは排他的な状態を保護しますが、あるテストの結果を別のテストに渡すものではありません。
レビューチェックリスト
- 追加するテストはすべて XCTest のアサーションではなく、Swift Testing(
@Test、#expect)を使用しています - テスト名は動作を表しています(
testFetchUserではなくfetchUserReturnsNilOnNetworkError) - エラー処理のテストを個別に用意しています
- 非同期テストでは
Task.sleepではなくconfirmation()を使用しています - 繰り返しのバリエーションにはパラメーター化テストを使用しています
- フィルタリング用にタグ(
.critical、.slow)を適用しています - モックは具象型をサブクラス化せず、プロトコルに準拠しています
- テスト間で共有の可変状態がありません
- テストは宣言順序や共有スイートインスタンスに依存しません
-
.serializedは、ワークフローの順序をモデル化するためではなく、本当に排他的な状態にのみ使用しています - キャンセル可能な非同期操作のキャンセルをテストしています
参考資料
- テストパターン:references/testing-patterns.md
- 高度なテスト(警告、キャンセル、画像の添付):references/testing-advanced.md