test-modernizer について
test-modernizerは、既存のSwiftテストを更新するためのスキルで、特にXCTestからSwift Testingへの移行を扱います。テスト構造、アサーション、並行実行、スキップ、添付ファイルの変換を案内し、移行対象外のケースも示します。
主な機能
- 01XCTestからSwift Testingへの移行
- 02テスト構造と初期化の変換
- 03アサーションとエラー確認の置換
- 04並行実行と共有状態への対応
- 05スキップと添付ファイルの変換
活用シーン
- 01既存のXCTestテストの移行
- 02Swift Testingテスト構成の改善
- 03反復テストのパラメーター化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ superagents-lab/ xcode27-skills --skill test-modernizerインストール後、Claude Code で/test-modernizerを入力して読み込まれたか確認します。
詳細
テストのモダナイズ
適用する場合:ユーザーがテストのモダナイズ、更新、移行、強化、変換を依頼したとき。 可能であれば XCTest を Swift Testing に移行します。既存の Swift Testing のテストについては、新しい機能を採用して、より適切に構成できるか評価します。
適用しない場合:ユーザーが既存の XCTest コードなしで新しいテストを一から作成するよう依頼したとき、XCTest の機能についてのみ質問しているとき、テスト結果やテストの実行についてのみ質問しているとき、テスト自体の更新ではなく新機能をカバーするテストの追加を依頼しているとき、移行に触れずにテストの失敗をデバッグしているとき、または XCUI* API を使用する UI 自動化テストがあるとき(これらは Swift Testing に移行できません)。
移行のリファレンス
インポート
import XCTest を import Testing に置き換えます。段階的に移行する途中でテスト内容が混在している場合は、1 つのファイルで両方をインポートできます。
import XCTest を削除する際は、そのファイルで Foundation の型(URL、CharacterSet、ProcessInfo、Data など)が使われているか確認します。XCTest は Foundation を再エクスポートするため、必要に応じて import Foundation を追加します。
テストクラスからスイートへ
XCTestCase の継承を削除します。class より struct を優先します。
final class FoodTruckTests: XCTestCase { ... }->struct FoodTruckTests { ... }
setUp/tearDown から init/deinit へ
override func setUp() を init()(async throws も可)に置き換えます。override func tearDown() は deinit に置き換えます。deinit が必要な場合は、struct には deinit がないため、struct の代わりに actor または final class を使用します。保存プロパティでは暗黙的にアンラップされるオプショナル型を使わないように変更し、初期値の代入を setUp から、インラインの初期化、または初期化が複雑な場合はイニシャライザーに移します。
struct MyTests {
var fixture = Fixture()
mutating func `Fixture behaves as expected`() {
#expect(fixture.doSomething())
}
}
インスタンス変数を関数本体に移すのは避けてください。冗長になることがあります。Swift Testing は、各テストの実行前にイニシャライザーを再度呼び出します。値セマンティクスを持つインスタンス変数をテストで変更する場合は、テスト関数に mutating を付ける必要があることがあります。
テストメソッド
test の名前プレフィックスを @Test 属性に置き換えます。置き換え後のテスト名に複数のキャメルケースの単語が含まれる場合は、文形式の名前を持つ生識別子を使用します。
func testEngineDoesNotStall() { ... }->@Test funcEngine does not stall() { ... }func testIgnition() { ... }->@Test func ignition() { ... }
テスト関数には async、throws、async throws を付けられます。また、@MainActor を使ってグローバルアクターに分離できます。
アサーションから期待値へ
XCTest から Swift Testing にテストを移行する際は、次の対応関係を適用します。
XCTAssert(x)、XCTAssertTrue(x) -> #expect(x)
XCTAssertFalse(x) -> #expect(!x)
XCTAssertNil(x) -> #expect(x == nil)
XCTAssertNotNil(x) -> #expect(x != nil)
XCTAssertEqual(x, y) -> #expect(x == y)
XCTAssertNotEqual(x, y) -> #expect(x != y)
XCTAssertIdentical(x, y) -> #expect(x === y)
XCTAssertNotIdentical(x, y) -> #expect(x !== y)
XCTAssertGreaterThan(x, y) -> #expect(x > y)
XCTAssertGreaterThanOrEqual(x, y) -> #expect(x >= y)
XCTAssertLessThanOrEqual(x, y) -> #expect(x <= y)
XCTAssertLessThan(x, y) -> #expect(x < y)
try XCTUnwrap(x) -> try #require(x)
XCTAssertEqual(_:_:accuracy:) に直接対応するものはありません。浮動小数点演算を直接使用してください。
エラー
エラー型が Equatable で、正確な値が分かっている場合は、特定のエラー値を確認する方法を優先します。
XCTAssertThrowsError(try f())
->
#expect(throws: (any Error).self) {
try f()
}
XCTAssertThrowsError(try f()) { error in
XCTAssertEqual(error, specificError)
}
->
#expect(throws: specificError) {
try f()
}
XCTAssertThrowsError(try f()) { error in
// Check error
}
->
let error = #expect(throws: (any Error).self) {
try f()
}
// Check error
XCTAssertNoThrow(try f())
->
#expect(throws: Never.self) {
try f()
}
continueAfterFailure
デフォルトでは continueAfterFailure は true です。これは、期待値に反してもテストの実行が停止しないことを意味します。
一部の XCTestCases では continueAfterFailure = false が設定されています。この場合、XCTAssert ファミリーの関数は
テストの実行を停止する Objective-C 例外をスローします。
テストメソッドで continueAfterFailure = false を設定した場合、この動作を維持するため、以降のすべてのアサーションで #expect(x) ではなく try #require(x)
を使用する必要があります。try #require(x) を追加する場合は、該当するメソッドに throws を追加してください。
setUp で continueAfterFailure = false を設定した場合、try #require(x) への変換は、そのクラスのすべてのテストメソッド内のすべてのアサーションに適用する必要があります。
Issue.record/XCTFail を期待値に置き換える
支障のない箇所では、Issue.record または XCTFail の使用を、テスト終了後の動作(continueAfterFailure を考慮)に応じて #expect または #require に置き換えてください。
場合によっては、期待値自体の内容で失敗の原因を十分に説明できるため、コメントは冗長になります。
たとえば、次の構造は以下のように置き換えてください。
guard let object = somethingOptional() else {
Issue.record("Could not get object")
return
}
guard object.isAvailable() else {
Issue.record("Object not available")
return
}
if !object.performOperation() {
Issue.record("Failed to perform operation")
}
->
let object = try #require(somethingOptional(), "Could not get object")
try #require(object.isAvailable())
#expect(object.performOperation())
非同期の期待値を確認に置き換える
XCTestExpectation + fulfill() + await fulfillment(of:) を confirmation() に置き換えてください。
// Before
let exp = expectation(description: "...")
handler = { exp.fulfill() }
doWork()
await fulfillment(of: [exp])
// After
await confirmation("...") { confirm in
handler = { confirm() }
doWork()
}
assertForOverFulfill = false と expectedFulfillmentCount を使用する場合は、範囲を指定してください。
await confirmation("...", expectedCount: 10...) { confirm in ... }
テストのスキップ
XCTSkipIf/XCTSkipUnless を、テストまたはスイートのトレイトに置き換えてください。
try XCTSkipIf(condition)->@Test(.disabled(if: condition))try XCTSkipUnless(condition)->@Test(.enabled(if: condition))
テストの途中で throw XCTSkip("reason") を使う場合は、try Test.cancel("reason") に置き換えてください。
OS バージョンまたはプラットフォームの利用可否を確認してスキップする場合は、.enabled(if:) ではなく、テスト関数に @available 属性を追加してください。
既知の問題
XCTExpectFailure("...", ...) { ... } を withKnownIssue("...") { ... } に置き換えてください。
断続的に発生する失敗については、.nonStrict() オプション(または省略形の strict: false パラメーター)を isIntermittent: true に置き換えてください。
条件指定/照合には、when: と matching: のパラメーターを使用してください。
withKnownIssue("...") {
try riskyOperation()
} when: {
shouldExpectFailure
} matching: { issue in
issue.error != nil
}
並行処理と直列実行
XCTest はデフォルトで、同期テストをメインアクター上で実行し、スイート内では順番に実行します。Swift Testing はすべてのテスト関数を任意のタスク上で並列に実行します。XCTest でのテストがメインアクターの分離に明示的に依存していた場合にのみ @MainActor を追加し、テストが共有状態に依存する場合は @Suite(.serialized) を追加してください。
添付ファイル
XCTAttachment + self.add(attachment) を Attachment.record(value) に置き換えてください。添付する型は Attachable に準拠している必要があります(Foundation をインポートしている場合、Codable および NSSecureCoding 型は自動的に準拠します)。
モダナイゼーションのガイドライン
- XCTest から移行する場合は、テストクラスを一度に 1 つずつ移行してください。移行中は、1 つのファイルに XCTest のテストと Swift Testing のテストの両方を含められます。
deinit(tearDown)が必要な場合を除き、テストスイートにはstructを優先してください。その場合はactorまたはfinal classを使用してください。@Testを追加する際は、メソッド名からtestプレフィックスを削除してください。文のように読める長めのテスト名には、生識別子構文を使用して読みやすさを高めてください。例:@Test funcAuthenticate, fetch summary, then check count() { ... }。- 既存の
@Test関数についても、複数単語からなるキャメルケースの名前を確認し、文形式の生識別子に変換してください。 - 生識別子構文は、文のように読める複数単語の名前にのみ使用してください。
setUpを移行する際は、暗黙的アンラップのオプショナルプロパティを、宣言時に初期化する非オプショナルプロパティに変換してください。初期化が複雑な場合、例外をスローする場合、または非同期の場合は、initで初期化してください。#expectまたは#requireに変換できる、明示的なXCTFail/Issue.recordの呼び出しがないか確認してください。try #requireの呼び出しを#expectに変更しないでください。テストの動作が変わります。- XCTest の暗黙的なメインアクター分離に明示的に依存していたテストにのみ、
@MainActorを追加してください。不必要に追加しないでください。 - 入力をループ処理するテストや、同じロジックを使う繰り返しのテストが多くないか確認し、
@Test(arguments:)を使ったパラメーター化テストに変換してください。 - テスト間で可変状態を共有するテストスイートには、
@Suite(.serialized)を追加し、structの代わりにactorまたはclassを使用することも検討してください。 #_sourceLocationなど、アンダースコアで始まるシンボルは使用しないでください。公開 API のみを使用してください。ソース位置には、常に完全なSourceLocation(fileID:filePath:line:column:)イニシャライザーを使用してください。