Swift Testing vs XCTest 比較

Appleの新しいオープンソーステストライブラリ——マクロベースで、Swift Concurrencyにネイティブ統合

VS
XCTest

約13年の実績を持つ、実戦で鍛えられたフレームワーク——UIテストとパフォーマンステストの唯一の選択肢

15 分で読了iOS

クイック結論

明確な傾向がある一方、『一括移行』は不要なリスクだ。新規の単体・統合テストはSwift Testingで書こう——コード量が減り、値の自動キャプチャ、組み込みのパラメータ化テスト、並列実行が手に入る。UI・パフォーマンステストはXCTestに残す。Appleのドキュメントがそれを求めている。両フレームワークは同一ターゲット内で共存でき、Swift 6.4がクロスフレームワークのレポーティングをもたらしたため、一括移行は不要——ファイル単位で進めよう。

Swift TestingXCTest
結論をすべて読む

スコア比較

グラフを読み込み中...

詳細スコア

詳細スコア: Swift Testing XCTest — カテゴリー別10点満点のスコア
カテゴリーSwift TestingXCTest
パフォーマンス
8/10
8/10
学習のしやすさ
7/10
6/10
エコシステム
6/10
8/10
コミュニティ
6/10
8/10
求人市場
5/10
7/10
将来性
9/10
6/10

長所と短所

Swift Testing

長所

  • #expectと#requireマクロにより1行で値を自動キャプチャでき、個別のアサーションファミリーを覚える必要がない
  • 組み込みのパラメータ化テストサポート(@Test(arguments:))——ループを書かずに1つのテストで複数シナリオをカバー
  • デフォルトで並列実行される。task groupベースのインプロセス並列処理により、テストスイートが高速化する
  • async throwsテスト関数がネイティブに書ける——Swift Concurrencyとの摩擦のない統合
  • テストキャンセル(Test.cancel())、警告レベルのissue記録、画像/Transferable添付のサポート(Swift 6.3〜6.4)
  • Appleプラットフォーム以外にも、公式ツールチェーンによりLinuxとWindowsでも動作する
  • オープンソース(Apache 2.0)——GitHub上で活発に開発され、コミュニティからの貢献を受け入れている
  • FB接頭辞のバグ識別子がApple Feedback Assistantに直接リンクされる

短所

  • UIオートメーション(XCUIApplication/XCUIElement)とパフォーマンステストをサポートしない——これらにはXCTestが必須
  • Xcode 16/Swift 6ツールチェーン未満のプロジェクトでは使用できず、古いXcodeバージョンには存在しない
  • エコシステムがまだ若い——サードパーティライブラリとStack Overflowの回答の蓄積はXCTestほど深くない
  • #expect内でスローされたエラーが後続のexpectationを静かに停止させることがあり、#requireやdo/catchによる規律が必要
  • デフォルトの並列実行は、共有状態(DBフィクスチャ、シングルトンなど)を持つ既存のテストコードでレースコンディションを表面化させる可能性がある

最適な用途

新規に開始するiOS/macOS/Linuxの単体・統合テスト多数の入力組み合わせを必要とするパラメータ化テストシナリオSwift Concurrency(async/await)を多用するコードベース高速な並列テストスイートを求める成長中のプロジェクトクロスプラットフォームのSwiftパッケージ(Linux/Windows CIを含む)

XCTest

長所

  • Xcode 5(2013年)以来のAppleの公式テストフレームワーク——エンタープライズプロジェクトで実証された安定性
  • XCUITestによるUIオートメーションとパフォーマンステストはXCTestにのみ存在する
  • 約13年分のStack Overflow、ブログ、書籍コンテンツによる最も深い知識ベース
  • Xcodeのテストレポート(xcresult)、Test Plan、CI統合と完全に融合している
  • Objective-Cを含む混在コードベースでは依然として唯一の必須選択肢
  • 大規模チームで誰もが知っている共通の構文——オンボーディングコストが低い
  • シナリオごとに個別のアサーションファミリー(Boolean/Nil/Equality/Comparable/Error)があり、明確で予測可能
  • パフォーマンステスト用にXCTMetricによる計測基盤が組み込まれている

短所

  • パラメータ化テスト用の組み込みAPIがない——ループやヘルパー関数で模倣する必要がある
  • XCTAssert*関数ファミリーは冗長で、Swift Testingのような値の自動キャプチャがない
  • テスト関数レベルでのインプロセスtask-group並列処理がなく、並列実行はシミュレータ/プロセスレベルで管理される
  • 非同期サポートは後から追加されたもの(XCTestExpectation/wait(for:))で、Swift ConcurrencyとはSwift Testingほど自然に統合されていない
  • WWDC24でのAppleの方針は、XCTestの役割をUIオートメーション、パフォーマンステスト、Objective-Cでしか書けないテストに限定している——新しい単体テストのエルゴノミクスはSwift Testing側で発展している
  • Objective-C由来のAPI表面は、新しいSwift開発者にとってより重く感じられる

最適な用途

XCUITestによるUIオートメーションテストXCTMetricベースのパフォーマンステストObjective-Cを含む、または古いXcodeバージョンをサポートするプロジェクト長期間運用されている大規模なレガシーテストスイートチーム全体で共通の成熟したテスト規約を必要とするエンタープライズプロジェクト

コード比較

Swift Testing
// Swift Testing — パラメータ化された非同期単体テスト
import Testing
@testable import PaymentKit

@Suite("価格計算")
struct PriceCalculatorTests {

    @Test("有効なクーポンコードで割引が正しく計算される",
          arguments: [
              ("SAVE10", 100.0, 90.0),
              ("SAVE20", 100.0, 80.0),
              ("NONE", 100.0, 100.0)
          ])
    func discountIsApplied(code: String, base: Double, expected: Double) async throws {
        let calculator = PriceCalculator()
        let result = try await calculator.applyDiscount(code: code, to: base)
        #expect(result == expected, "\(code) の期待値は \(expected)、実際は \(result)")
    }

    @Test("無効なクーポンはエラーを投げる")
    func invalidCouponThrows() async throws {
        let calculator = PriceCalculator()
        await #expect(throws: CouponError.invalid) {
            try await calculator.applyDiscount(code: "XXX", to: 100.0)
        }
    }

    @Test("カート合計が必要", .disabled("カートサービスはまだ移行されていない"))
    func cartTotalRequired() async throws {
        let calculator = PriceCalculator()
        try #require(calculator.cartTotal > 0)
    }
}
XCTest
// XCTest — 同じシナリオ(価格計算)+ UIテスト
import XCTest
@testable import PaymentKit

final class PriceCalculatorTests: XCTestCase {
    var calculator: PriceCalculator!

    override func setUpWithError() throws {
        calculator = PriceCalculator()
    }

    func testDiscountIsAppliedForKnownCoupons() async throws {
        let cases: [(String, Double, Double)] = [
            ("SAVE10", 100.0, 90.0),
            ("SAVE20", 100.0, 80.0),
            ("NONE", 100.0, 100.0)
        ]
        for (code, base, expected) in cases {
            let result = try await calculator.applyDiscount(code: code, to: base)
            XCTAssertEqual(result, expected, "クーポン: \(code)")
        }
    }

    // XCTAssertThrowsErrorは同期的なので、非同期呼び出しではdo/catch + XCTFailを使用する
    func testInvalidCouponThrows() async throws {
        do {
            _ = try await calculator.applyDiscount(code: "XXX", to: 100.0)
            XCTFail("エラーが期待されていた")
        } catch {
            XCTAssertEqual(error as? CouponError, .invalid)
        }
    }
}

// UIテスト — XCTest/XCUITestでのみ可能
final class CheckoutUITests: XCTestCase {
    func testApplyCouponButtonUpdatesTotal() throws {
        let app = XCUIApplication()
        app.launch()
        app.textFields["couponField"].tap()
        app.textFields["couponField"].typeText("SAVE10")
        app.buttons["applyCouponButton"].tap()
        XCTAssertEqual(app.staticTexts["totalLabel"].label, "$90.00")
    }
}

結論

明確な傾向がある一方、『一括移行』は不要なリスクだ。新規の単体・統合テストはSwift Testingで書こう——コード量が減り、値の自動キャプチャ、組み込みのパラメータ化テスト、並列実行が手に入る。UI・パフォーマンステストはXCTestに残す。Appleのドキュメントがそれを求めている。両フレームワークは同一ターゲット内で共存でき、Swift 6.4がクロスフレームワークのレポーティングをもたらしたため、一括移行は不要——ファイル単位で進めよう。

無料相談を受ける
FAQ

よくある質問

いいえ。Swift Testingは新規の単体・統合テストにおいてAppleが推奨するデフォルトですが、XCTestが廃止されたわけではありません。XCUITest(UIオートメーション)とXCTMetric(パフォーマンステスト)は依然としてXCTestのみに存在します。両者は同一ターゲット内で共存でき、Swift 6.4以降はクロスフレームワークのレポーティングによって連携できます。

関連ブログ記事

すべての記事を見る
すべての比較