swift-testing について
XCTest からの移行を考える Swift 開発者向けの Swift Testing ガイドです。マクロによるアサーション、非同期・パラメータ化テスト、移行時の注意点を扱います。
主な機能
- 01#expect と #require によるマクロアサーション
- 02非同期テストとコールバック確認
- 03引数を組み合わせるパラメータ化テスト
- 04テストタグと並列実行の注意点
- 05XCTest アサーションの移行方法
活用シーン
- 01Swift のテストスイートで XCTest アサーションを置き換える
- 02非同期関数やコールバックの動作をテストする
- 03パラメータ化テスト用に入力値をペアにする
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ johnrogers/ claude-swift-engineering --skill swift-testingインストール後、Claude Code で/swift-testingを入力して読み込まれたか確認します。
詳細
Swift Testing フレームワーク
Swift Testing フレームワークを使ったモダンなテストです。XCTest は使用しません。
概要
Swift Testing は XCTest を、より簡潔で、非同期処理のサポートに優れ、デフォルトでテストを並列実行する、マクロベースのモダンなアプローチに置き換えます。基本原則は、XCTest を学んだなら忘れることです。Swift Testing は異なる仕組みで動作します。
リファレンス
基本概念
アサーション
| マクロ | 用途 |
|---|---|
#expect(expression) | ソフトチェック — 失敗しても続行します。ほとんどのアサーションに使用します。 |
#require(expression) | ハードチェック — 失敗するとテストを停止します。前提条件にのみ使用します。 |
オプショナルのアンラップ
let user = try #require(await fetchUser(id: "123"))
#expect(user.id == "123")
テスト構造
import Testing
@testable import YourModule
@Suite
struct FeatureTests {
let sut: FeatureType
init() throws {
sut = FeatureType()
}
@Test("Description of behavior")
func testBehavior() {
#expect(sut.someProperty == expected)
}
}
アサーションの置き換え
| XCTest | Swift Testing |
|---|---|
XCTAssert(expr) | #expect(expr) |
XCTAssertEqual(a, b) | #expect(a == b) |
XCTAssertNil(a) | #expect(a == nil) |
XCTAssertNotNil(a) | #expect(a != nil) |
try XCTUnwrap(a) | try #require(a) |
XCTAssertThrowsError | #expect(throws: ErrorType.self) { } |
XCTAssertNoThrow | #expect(throws: Never.self) { } |
エラーのテスト
#expect(throws: (any Error).self) { try riskyOperation() }
#expect(throws: NetworkError.self) { try fetch() }
#expect(throws: NetworkError.timeout) { try fetch() }
#expect(throws: Never.self) { try safeOperation() }
パラメーター化テスト
@Test("Validates inputs", arguments: zip(
["a", "b", "c"],
[1, 2, 3]
))
func testInputs(input: String, expected: Int) {
#expect(process(input) == expected)
}
警告: zip を使わずに複数のコレクションを指定すると、直積が生成されます。
非同期テスト
@Test func testAsync() async throws {
let result = try await fetchData()
#expect(!result.isEmpty)
}
確認
@Test func testCallback() async {
await confirmation("callback received") { confirm in
let sut = SomeType { confirm() }
sut.triggerCallback()
}
}
タグ
extension Tag {
@Tag static var fast: Self
@Tag static var networking: Self
}
@Test(.tags(.fast, .networking))
func testNetworkCall() { }
よくある落とし穴
#requireの過剰な使用 — ほとんどのチェックには#expectを使用します- 状態の分離を忘れる — 各テストには新しいインスタンスが割り当てられます
- 意図しない直積 — 対応する入力には必ず
zipを使用します .serializedを使わない — スレッドセーフでない既存テストには適用します
よくある間違い
-
#requireの過剰な使用 —#requireは前提条件にのみ使用します。通常のアサーションに使うと、すべての失敗が報告される代わりに、最初の失敗でテストが停止します。アサーションには#expectを使い、後続のアサーションがその値に依存する場合に限って#requireを使用します。 -
直積によるバグ —
@Test(arguments: [a, b], [c, d])は2つではなく、4つの組み合わせを生成します。引数を正しく対応させるには、必ずzipを使用します。例:arguments: zip([a, b], [c, d])。 -
状態の分離を忘れる — Swift Testing はテストメソッドごとに新しいテストインスタンスを作成します。ただし、テスト間で共有される状態(static 変数やシングルトン)は引き続き漏れます。依存性注入を使うか、テスト間でシングルトンをクリーンアップしてください。
-
並列テストの競合 — Swift Testing はデフォルトでテストを並列実行します。共有ファイル、データベース、シングルトンにアクセスするテストは互いに干渉します。
.serializedまたは分離戦略を使用してください。 -
asyncを自然に使わない — 非同期処理をTask { }でラップすると、本来の利点が失われます。テスト関数のシグネチャでasync/awaitを直接使用します。例:@Test func testAsync() async throws { }。 -
Confirmation の誤用 —
confirmationはコールバックが呼び出されたことを検証するためのものです。アサーションに使うのは誤りです。アサーションには#expectを、コールバックの呼び出し回数にはconfirmationを使用します。