WWDC Quick Look 💓 By SwiftGGTeam
Meet Swift Testing

Meet Swift Testing

元の動画を見る

ハイライト

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 はグローバル関数や型のメソッドに付けられ、asyncthrows、グローバル 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 を代替、initsetUpdeinittearDown(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)
}

キーポイント:

  • 関数シグネチャにパラメータを追加、@Testarguments: で入力値を指定
  • Test Navigator では各パラメータ値が独立したテストエントリとして表示、個別に再実行・デバッグ可能
  • for…in ループより優れている — 各パラメータの結果が独立して可視、並列実行をサポート、失敗したパラメータのみ再実行可能

XCTest との関係

両方は同じテスト target 内で共存可能(17:36)。UI 自動化(XCUIApplication)とパフォーマンステスト(XCTMetric)は引き続き XCTest が必要。移行のヒント:構造が似た複数の XCTest メソッドは 1 つのパラメータ化テストに統合可能、テストメソッドが 1 つだけの XCTestCase はグローバル @Test 関数に移行可能。Swift Testing テストで XCTAssert 関数を呼ばず、XCTestCase で #expect を呼ばないこと。


重要ポイント

  1. パラメータ化テストで重複テストコードを排除:プロジェクト内の「同じロジック、異なる入力」のテスト関数は保守負担です。パラメータ化テストに統合すれば、新しいテストケースは arguments 配列に 1 行追加するだけ。始め方:関数名のサフィックスで区別されるテスト(testVideoAtestVideoB など)を見つけ、共通ロジックをパラメータ化関数に抽出。

  2. ファイルディレクトリの代わりに .tags でテストを整理:従来はフォルダでグループ化しますが、モジュール間の関連は表現しにくい。.tags はファイルや target をまたいでマークでき、Test Navigator でタグ別にフィルタ・実行、Test Report で同一タグの複数テスト同時失敗の関連インサイトを確認可能。始め方:各コア機能/サブシステムに Tag を定義し、既存テストに段階的にタグ付け。

  3. コメントアウトされたテストの代わりに .disabled + .bug を使用:コメントアウトされたテストはコンパイルに参加せず、コードが腐っている可能性がある。.disabled は常にコンパイル可能、.bug は問題リンクで無効化理由を追跡可能。始め方:コメントアウトされたテスト関数をすべて検索し、.disabled("理由").bug("リンク") に置き換え。

  4. 新プロジェクトは Swift Testing を直接使用、旧プロジェクトは段階的導入:両フレームワークは同じ target 内で共存可能、新 target の作成は不要。既存 XCTestCase の横に新しい @Test 関数を追加して #expect の診断情報を体験し、チームが慣れたら段階的に移行。始め方:既存テスト target で Testing を import し、新しい @Test 関数を 1 つ書いてフローを確認。


関連セッション

コメント

GitHub Issues · utterances