ハイライト
Swift Testing は今年登場した新しいオープンソーステストパッケージです。XCTest と同じ target 内で共存でき、Xcode 16 で新しいテスト target を作成するとデフォルトで選択されます。
主要内容
テストを書くうえで最も面倒なのはテスト自体ではなく、XCTest のボイラープレートです。各テストは XCTestCase を継承し、メソッド名は test で始まり、XCTAssertXxx の関数シグネチャを覚え、setUp/tearDown のライフサイクルにも注意が必要です。これらは「テストロジック」ではなく、フレームワークの負担です。
Swift Testing はこのフレームワークノイズを最小限に抑えます。必要なのは @Test で関数をマークし、#expect でアサーションを書くだけ。クラス継承も、十数種類のアサーション関数の暗記も不要 — #expect は通常の Swift 式を受け取り、失敗時にサブ式の値を自動キャプチャします。Xcode 16 で新しいテスト target を作成すると、Swift Testing がすでにデフォルトオプションです。
フレームワークの 4 つの構成要素は、@Test がテスト関数をマーク、#expect/#require がアサーションを実行、Trait がテスト動作をカスタマイズ、Suite がテスト構造を整理します。Suite 内の各テスト関数は実行時に独立したインスタンスを作成し、XCTestCase でよくある状態共有問題を回避します。フレームワーク全体はオープン開発プロセスを採用し、コミュニティが GitHub で貢献と提案に参加できます。
詳細
最初のテストを書く
Testing モジュールをインポートし、グローバル関数に @Test を付け、#expect でアサートします(01:54):
import Testing
@testable import DestinationVideo
@Test func videoMetadata() {
let video = Video(fileName: "By the Lake.mov")
let expectedMetadata = Metadata(duration: .seconds(90))
#expect(video.metadata == expectedMetadata)
}
キーポイント:
import Testingでフレームワークを導入、@testable importは internal メンバーにアクセスする Swift 言語機能@Testはグローバル関数や型のメソッドに付けられ、async、throws、グローバル actor 分離をサポート#expectは通常の式と演算子を受け取り、失敗時にソースと左右の値を自動キャプチャ
#require:テストの早期終了
#require は #expect の厳格版で、Optional のアンラップやテストを早期終了する必要がある場面向け(02:35):
@Test(.tags(.formatting)) func formattedDuration() async throws {
let videoLibrary = try await VideoLibrary()
let video = try #require(await videoLibrary.video(named: "By the Lake"))
#expect(video.formattedDuration == "0m 19s")
}
キーポイント:
#requireにはtryが必要。式が false または Optional が nil のときエラーを throw してテストを終了- 後続コードが前の結果に依存する場面に適 — 値が存在しないときに続行しても意味がない
Trait:テスト動作のカスタマイズ
Trait は 3 番目の構成要素で、説明情報の追加、実行条件の制御、動作の変更が可能(06:06):
@Test(.disabled("Due to a known crash"),
.bug("example.org/bugs/1234", "Program crashes at <symbol>"))
func example() {
// ...
}
キーポイント:
.disabled(...)はテストを無効化しつつコードはコンパイル可能 — 関数をコメントアウトするより安全.bug(...)は問題追跡リンクを関連付け、Xcode 16 の Test Report から直接クリック可能.enabled(if:)で実行条件を制御、条件を満たさない場合は skipped.tags(...)でテストにラベルを付け、ファイルや target をまたいでグループ化、Test Navigator でタグ別に表示・実行
Suite:テスト構造の整理
struct でテスト関数をラップして Suite を形成(07:18):
@Suite(.tags(.formatting))
struct MetadataPresentation {
let video = Video(fileName: "By the Lake.mov")
@Test func rating() async throws {
#expect(video.contentRating == "G")
}
@Test func formattedDuration() async throws {
let videoLibrary = try await VideoLibrary()
let video = try #require(await videoLibrary.video(named: "By the Lake"))
#expect(video.formattedDuration == "0m 19s")
}
}
キーポイント:
- Suite は struct を推奨 — 値セマンティクスで状態共有を回避、各
@Testメソッドは独立した Suite インスタンスを作成 - ストアドプロパティが従来の
setUpを代替、initがsetUp、deinitがtearDown(deinit は class または actor が必要) @Suite属性で display name と trait を指定可能、子 Suite と内部テストは継承
パラメータ化テスト
繰り返しのテスト関数を 1 つのパラメータ化テストに統合(14:07):
@Test("Number of mentioned continents", arguments: [
"A Beach",
"By the Lake",
"Camping in the Woods",
"The Rolling Hills",
"Ocean Breeze",
"Patagonia Lake",
"Scotland Coast",
"China Paddy Field",
])
func mentionedContinentCounts(videoName: String) async throws {
let videoLibrary = try await VideoLibrary()
let video = try #require(await videoLibrary.video(named: videoName))
#expect(!video.mentionedContinents.isEmpty)
#expect(video.mentionedContinents.count <= 3)
}
キーポイント:
- 関数シグネチャにパラメータを追加、
@Testのarguments:で入力値を指定 - Test Navigator では各パラメータ値が独立したテストエントリとして表示、個別に再実行・デバッグ可能
- for…in ループより優れている — 各パラメータの結果が独立して可視、並列実行をサポート、失敗したパラメータのみ再実行可能
XCTest との関係
両方は同じテスト target 内で共存可能(17:36)。UI 自動化(XCUIApplication)とパフォーマンステスト(XCTMetric)は引き続き XCTest が必要。移行のヒント:構造が似た複数の XCTest メソッドは 1 つのパラメータ化テストに統合可能、テストメソッドが 1 つだけの XCTestCase はグローバル @Test 関数に移行可能。Swift Testing テストで XCTAssert 関数を呼ばず、XCTestCase で #expect を呼ばないこと。
重要ポイント
-
パラメータ化テストで重複テストコードを排除:プロジェクト内の「同じロジック、異なる入力」のテスト関数は保守負担です。パラメータ化テストに統合すれば、新しいテストケースは arguments 配列に 1 行追加するだけ。始め方:関数名のサフィックスで区別されるテスト(
testVideoA、testVideoBなど)を見つけ、共通ロジックをパラメータ化関数に抽出。 -
ファイルディレクトリの代わりに .tags でテストを整理:従来はフォルダでグループ化しますが、モジュール間の関連は表現しにくい。
.tagsはファイルや target をまたいでマークでき、Test Navigator でタグ別にフィルタ・実行、Test Report で同一タグの複数テスト同時失敗の関連インサイトを確認可能。始め方:各コア機能/サブシステムに Tag を定義し、既存テストに段階的にタグ付け。 -
コメントアウトされたテストの代わりに .disabled + .bug を使用:コメントアウトされたテストはコンパイルに参加せず、コードが腐っている可能性がある。
.disabledは常にコンパイル可能、.bugは問題リンクで無効化理由を追跡可能。始め方:コメントアウトされたテスト関数をすべて検索し、.disabled("理由").bug("リンク")に置き換え。 -
新プロジェクトは Swift Testing を直接使用、旧プロジェクトは段階的導入:両フレームワークは同じ target 内で共存可能、新 target の作成は不要。既存 XCTestCase の横に新しい
@Test関数を追加して#expectの診断情報を体験し、チームが慣れたら段階的に移行。始め方:既存テスト target で Testing を import し、新しい@Test関数を 1 つ書いてフローを確認。
関連セッション
- Go further with Swift Testing — パラメータ化テストの高度な使い方、タグと trait の詳細
- Migrate a test from XCTest to Swift Testing — 既存 XCTest テストを Swift Testing に移行するステップバイステップデモ
- What’s new in Xcode 16 — Swift Testing IDE 統合を含む Xcode 16 新機能の総覧
- Extend your Xcode Cloud workflows — Xcode Cloud が新しいテストフレームワークにどう適応するか
コメント
GitHub Issues · utterances