今回は現在参画中のプロジェクトでも導入が進んできたiOSのApple公式テストフレームワーク「Swift Testing」について、誕生の歴史から導入方法、便利で強力な機能などを具体的なコード例などを交え、一貫してことはじめ的に解説していこうかと思います。
それでは、早速見ていきましょう!
これまでのiOSテストの歩み
長年、iOSにおける単体テストのデファクトスタンダードは XCTest でした。
iOSの開発がObjective-Cだった頃から続く安定で強力なテストフレームワークですが、
Swift言語が進化し、モダンな機能(Result Builders, Concurrency, Macros等)を取り入れていく中で、XCTestの設計思想には少しずつ「時代とのズレ」が生じていました。
- クラス継承(XCTestCase)を強制されることによる制約
- 値型(Value Type)中心のSwift設計との不一致
- 非同期処理テストの記述の冗長性
これらの課題を一挙に解決すべく、WWDC2024で正式リリースされたのが、新しいテストフレームワーク「Swift Testing」です。
1. なぜXCTest → Swift Testing なのか?
まず、なぜAppleは既存のXCTestを拡張するのではなく、ゼロからフレームワークを作り直したのか。
その背景にある技術的な必然性を理解しておきましょう。
Objective-Cランタイムからの脱却
XCTestは、その根底にObjective-Cのランタイム依存を持っています。
テストメソッドをランタイムで見つけ出し実行する仕組みは動的で柔軟ですが、Swiftの「静的安全性」や「コンパイル時の最適化」とは相性が悪い側面がありました。
Swift Testingは、Swift 5.9で導入された マクロ(Macros) 技術を基盤となっています。
テストの検出や検証ロジックの展開をコンパイル時に行うことで、実行時のオーバーヘッドを減らし、より詳細なエラーレポートを可能としています。
並列実行(Parallel Execution)がデフォルトに
XCTestでも並列実行は可能でしたが、設定が複雑だったり、シミュレーターの起動コストがかかったりと、手軽とは言えませんでした。
Swift Testingでは、デフォルトでテストが並列に実行されます。
これは、Swiftの並行処理モデル Swift Concurrency を前提に設計されているからこそ実現できた機能です。
2. よりSwiftライクな記述へ
それでは、具体的なコードを見ながら、XCTestからどこが進化したのか見ていきましょう。
命名規則の撤廃とマクロの導入
XCTestの場合
final class CalculatorTests: XCTestCase {
// 関数名は "test" で開始必須
func testAddition_Success() { ... }
}Swift Testingの場合
import Testing
struct CalculatorTests {
// マクロで明示。関数名は自由(日本語も可)
@Test("足し算の正常系テスト")
func addition() { ... }
}@Test マクロには、テストの表示名(Display Name)を引数として渡すことができます。
これにより、テスト結果一覧には「testAddition」ではなく「足し算の正常系テスト」と表示され、何のためのテストなのかが一目で理解できるようになります。
#expect : アサーションの表現力向上
XCTestでは、検証内容に応じてメソッドを使い分ける必要がありました。
- XCTAssertEqual
- XCTAssertTrue
- XCTAssertNil
- XCTAssertLessThan
など、多くのメソッドを記憶しなければなりませんし、メソッドが異なれば振る舞いにも気を使う必要が出てきます。
しかし、Swift Testingでは、 #expect(...) ひとつで完結できます。
let result = 1 + 1
#expect(result == 2)
let user = User(name: "RightCode")
#expect(user.name.contains("Code"))特筆すべきは、テスト失敗時のログ出力です。
#expect マクロはコンパイル時に式を展開し、評価された値の情報を埋め込みます。
その結果、失敗時には以下のような詳細なログが出力されます。
Expectation failed: (user.name.contains("Code")) with user.name = "WrongName"「何と比較してどう違ったのか」を自分でログ出力しなくても、フレームワークが全て出力してくれるんですよね。
#require : 必須要件の検証
テスト実行中に、オプショナル値が nil だった場合、それ以降のテストを続けても意味がないことがあります。
この時に利用するのが、 #require(...) です。(XCTest の XCTUnwrap に相当します)
@Test func userProfile() throws {
let user: User? = fetchUser()
// nilならここでテストを中断(Fail)し、例外を投げる
let validUser = try #require(user)
// 以降、validUserは非オプショナル型として扱える
#expect(validUser.isActive)
}#expect が「検証に失敗してもテストを続行する(Soft Assertion)」のに対し、try #require は「前提条件が満たされないなら即座に止める(Hard Assertion)」という明確な使い分けができます。
3. 構造体(Struct)中心の設計
Swift Testingの大きな特徴の一つが、テストを 構造体(Struct) や アクター(Actor) で定義できる点です。
これは単なる好みの問題ではなく、テストの安全性に大きく関係します。
…
記事の続きは下のURLをクリック!
https://rightcode.co.jp/blogs/54431
もっとワクワクしたいあなたへ
現在、ライトコードでは「WEBエンジニア」「モバイルエンジニア」「ゲームエンジニア」、「デザイナー」「WEBディレクター」「営業」などを積極採用中です!
ライトコードは技術力に定評のある受託開発をメインにしているIT企業です。
有名WEBサービスやアプリの受託開発などの企画、開発案件が目白押しの状況です。
- もっと大きなことに挑戦したい!
- エンジニアとしてもっと成長したい!
- モダンな技術に触れたい!
現状に満足していない方は、まずは、エンジニアとしても第一線を走り続ける弊社代表と気軽にお話してみませんか?
ネット上では、ちょっとユルそうな会社に感じると思いますが(笑)、
実は技術力に定評があり、沢山の実績を残している会社ということをお伝えしたいと思っております。
- ライトコードの魅力を知っていただきたい!
- 社風や文化なども知っていただきたい!
- 技術に対して熱意のある方に入社していただきたい!
一度、【Wantedly内の弊社ページ】をのぞいてみてください。
Wantedly:https://www.wantedly.com/companies/rightcode