3.9K Views
October 18, 24
スライド概要
Mobile Act OSAKA 14 - connpass
https://mobileact.connpass.com/event/330822/
#MobileAct でSwift Testingの話をしました - usami-kの日記
https://usami-k.hatenablog.com/entry/2024/10/19/111357
https://usami-k.github.io/
Swift Testing を活用する 宇佐見公輔 2024-10-18 株式会社ゆめみ
自己紹介 • 宇佐見公輔 • 株式会社ゆめみ ‣ iOS テックリード • 50 歳になりました ‣ 節目なので、健康診断で MRI とか CT スキャンとか 1 / 31
最近のアウトプット • Swift Testing ‣ 関モバ #5 • 3 次元回転とクォータニオン / Accelerate ‣ iOSDC ポスターセッション / Mobile Act OSAKA 13 • iOS アプリ開発の知識 ‣ iOSDC パンフレット記事 • Apple Vision Pro の UI / フォーカス操作 ‣ 関モバ #4 / YUMEMI.grow Mobile #15 2 / 31
Swift Testing とは
Swift Testing とは Swift 用の単体テストフレームワーク • Swift 公式の GitHub リポジトリで公開されている ‣ https://github.com/swiftlang/swift-testing • Xcode 16 に統合された • XCTest との関係 ‣ 従来の XCTest に比べて、Swift の機能をより活用 ‣ XCTest と同じプロジェクトで混在可能 Swift Testing とは 4 / 31
Swift Testing の構成要素 • テスト関数 ‣ @Test 属性 • 期待値の確認 ‣ #expect マクロ • テストスイート ‣ @Suite 属性 • トレイト ‣ TestTrait / SuiteTrait Swift Testing とは 5 / 31
テスト関数
テスト関数定義:XCTest import XCTest class FoodTruckTests: XCTestCase { func testEngineWorks() { // ... } } • XCTestCase のサブクラス内で定義する必要がある • メソッド名を test 始まりで命名する必要がある テスト関数 7 / 31
テスト関数定義:Swift Testing import Testing struct FoodTruckTests { @Test func engineWorks() { // ... } } • テスト関数はどこで定義してもよい • メソッドに @Test 属性をつければテスト関数になる テスト関数 8 / 31
期待値の確認
期待値の確認 // XCTest func testEngineWorks() throws { XCTAssertNotNil(engine.parts.first) XCTAssertGreaterThan(engine.batteryLevel, 0) XCTAssertTrue(engine.isRunning) } // Swift Testing @Test func engineWorks() throws { try #require(engine.parts.first != nil) #expect(engine.batteryLevel > 0) #expect(engine.isRunning) } 期待値の確認 10 / 31
Xcode 上の表示 #expect 期待値の確認 マクロはテスト失敗時の結果をきれいに表示する 11 / 31
テストスイート
テストスイート @Suite struct FoodTruckTests { @Test func engineWorks() { // ... } } • テスト関数を含む型が、自動的にテストスイートになる • 後述するトレイトを使う場合は @Suite 属性を指定する テストスイート 13 / 31
Swift の機能の活用 final class FoodTruckTests { init() async throws { // ... } } • 専用の setUp の代わりに、通常の init が使える • actor や @MainActor なども使える テストスイート 14 / 31
トレイト
トレイト テスト関数やテストスイートの振る舞いを指定する • テスト関数のタイムアウト時間を指定する例 @Test(.timeLimit(.seconds(30)) func serveLargeOrder() { // ... } トレイト 16 / 31
用意されているトレイト • .enabled / .disabled • .timeLimit • .serialized • .tags • .bug • .isRecursive トレイト 17 / 31
Swift Testing の機能
Swift Testing の機能 Swift Testing ならではの機能をいくつか紹介 • エラーのテスト • パラメトライズテスト • テストの並列実行 Swift Testing の機能 19 / 31
エラーのテスト
エラーのテスト
var order = PizzaToppings(bases: [.calzone, .deepCrust])
#expect(throws: PizzaToppings.Error.outOfRange) {
try order.add(topping: .mozarella,
toPizzasIn: -1..<0)
}
• 自前で do 〜 catch するテスト実装が不要に
• throw されなければテスト失敗になってくれる
‣ 自前でテスト実装したときにやりがちなミス
エラーのテスト
21 / 31
エラーのテストのカスタマイズ #expect { FoodTruck.shared.engine.batteryLevel = 0 try FoodTruck.shared.engine.start() } throws: { error in return error == EngineFailureError.batteryDied || error == EngineFailureError.stillCharging } • 単純な等価判定では都合が悪い場合は errorMatcher を実装 ‣ エラーが Equatable でない場合など エラーのテスト 22 / 31
パラメトライズテスト
パラメトライズテスト enum Food { case burger, iceCream, burrito, noodleBowl, kebab } @Test(arguments: [ Food.burger, .iceCream, .burrito, .noodleBowl, .kebab ]) func foodAvailable(_ food: Food) async throws { let foodTruck = FoodTruck(selling: food) #expect(await foodTruck.cook(food)) } パラメトライズテスト 24 / 31
Xcode 上の表示 Xcode のテストナビゲータで、パラ メータごとに分かれて表示される • それぞれのテスト結果がわかる • 特定パラメータだけで実行できる パラメトライズテスト 25 / 31
テストの並列実行
テストの並列実行 • デフォルトで、テストは並列実行される • テスト関数 ‣ パラメトライズテストの場合、各テストが並列実行 • テストスイート ‣ 各テスト関数やサブスイートが並列実行 テストの並列実行 27 / 31
テストの直列実行 グローバルな状態を操作するテストは並列実行だと困る @Suite(.serialized) struct FoodTruckTests { @Test func refill() { ... } @Test func startEngine() async throws { ... } } • .serialized トレイトで並列でなく直列実行にできる テストの並列実行 28 / 31
複数テストスイートの直列実行 @Suite(.serialized) struct MultipleTests {} extension MultipleTests { struct FoodTruckTests { ... } } extension MultipleTests { struct OtherTests { ... } } • サブスイートも直列実行にできる • なお、.serialized トレイトはサブスイートにも適用される テストの並列実行 29 / 31
まとめ
まとめ • Swift Testing の構成要素 ‣ テスト関数、期待値の確認、テストスイート、トレイト • Swift Testing の機能 ‣ エラーのテスト ‣ パラメトライズテスト ‣ テストの並列実行 まとめ 31 / 31