2K Views
October 24, 24
スライド概要
YUMEMI.grow Mobile #17 - connpass
https://yumemi.connpass.com/event/331368/
YUMEMI.grow Mobile で Swift Testing の話をしました #yumemi_grow - usami-kの日記
https://usami-k.hatenablog.com/entry/2024/10/24/231046
https://usami-k.github.io/
Swift Testing による エラーのテスト 宇佐見公輔 2024-10-24 株式会社ゆめみ
自己紹介 • 宇佐見公輔 ‣ 株式会社ゆめみ iOS テックリード • 最近のアウトプット ‣ Swift Testing を活用する(Mobile Act OSAKA 14) ‣ XCTest から Swift Testing へ(関モバ A #5) ‣ 3 次元回転とクォータニオン(iOSDC Japan 2024) 1 / 28
Swift Testing の技術同人誌 • Swift Testing の技術同人誌を制作中 • 11 月 2 日(土)からの技術書典 17 に出展予定 ‣(今回はオンラインのみの出展) 2 / 28
Swift Testing とは
Swift Testing とは Swift 用の単体テストフレームワーク • Swift 公式の GitHub リポジトリで公開されている ‣ https://github.com/swiftlang/swift-testing • Xcode 16 に統合された • XCTest との関係 ‣ 従来の XCTest に比べて、Swift の機能をより活用 ‣ XCTest と同じプロジェクトで混在可能 Swift Testing とは 4 / 28
Swift Testing の構成要素 • テスト関数 ‣ @Test 属性 • 期待値の確認 ‣ #expect マクロ • テストスイート ‣ @Suite 属性 • トレイト ‣ TestTrait / SuiteTrait Swift Testing とは 5 / 28
テスト関数
テスト関数定義:XCTest import XCTest class FoodTruckTests: XCTestCase { func testEngineWorks() { // ... } } • XCTestCase のサブクラス内で定義する必要がある • メソッド名を test 始まりで命名する必要がある テスト関数 7 / 28
テスト関数定義:Swift Testing import Testing struct FoodTruckTests { @Test func engineWorks() { // ... } } • テスト関数はどこで定義してもよい • メソッドに @Test 属性をつければテスト関数になる テスト関数 8 / 28
期待値の確認
期待値の確認 // 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 / 28
Xcode 上の表示 #expect 期待値の確認 マクロはテスト失敗時の結果をきれいに表示する 11 / 28
テストスイート
テストスイート struct FoodTruckTests { @Test func engineWorks() { // ... } } • テスト関数を含む型が、自動的にテストスイートになる • 後述するトレイトを使う場合は @Suite 属性を指定する テストスイート 13 / 28
Swift の機能の活用 final class FoodTruckTests { init() async throws { // ... } deinit { // ... } } • 専用の setUp の代わりに、通常の init が使える • actor や @MainActor なども使える テストスイート 14 / 28
トレイト
トレイト テスト関数やテストスイートの振る舞いを指定する @Suite(.timeLimit(.minutes(1))) struct FoodTruckTests { @Test func engineWorks() { // ... } } • テストスイートのタイムアウト時間を指定する例 • ちなみに TimeLimitTrait.Duration は .minutes 指定のみ トレイト 16 / 28
用意されているトレイト • .enabled / .disabled • .timeLimit • .serialized • .tags • .bug • .isRecursive トレイト 17 / 28
Swift Testing の機能
Swift Testing の機能 Swift Testing ならではの機能 • エラーのテスト • パラメトライズテスト • テストの並列実行 今回は、エラーのテストについて紹介 Swift Testing の機能 19 / 28
エラーのテスト
エラーのテスト:XCTest • 素朴に do 〜 catch でテストを書いた場合 func testExample() throws { let myModel = MyModel() do { try myModel.doSomething() XCTFail("Expect to throw error") } catch { XCTAssertEqual(error as? MyError, .someError) } } エラーのテスト 21 / 28
エラーのテスト:XCTest • XCTAssertThrowsError を使うとより安全に書ける func testExample2() throws { let myModel = MyModel() XCTAssertThrowsError( try myModel.doSomething() ) { error in XCTAssertEqual(error as? MyError, .someError) } } エラーのテスト 22 / 28
エラーのテスト:Swift Testing • #expect マクロで書ける @Test func example() throws { let myModel = MyModel() #expect(throws: MyError.someError) { try myModel.doSomething() } } エラーのテスト 23 / 28
エラーのテスト:Swift Testing • 特定の型のエラーであるかを確認 #expect(throws: MyError.self) { ... } • 特定のエラーインスタンスと一致するかを確認 ‣ この場合、エラー型が Equatable であることが必要 #expect(throws: MyError.someError) { ... } エラーのテスト 24 / 28
エラーのテストのカスタマイズ • エラー判定をカスタマイズしたい場合 ‣ エラーが Equatable でない場合などに有益 @Test func example2() throws { let myModel = MyModel() #expect { try myModel.doSomething() } throws: { error in return error as? MyError == .someError } } エラーのテスト 25 / 28
エラーが発生しないことのテスト • 単にコードをテスト関数の中に書けばよい @Test func example3() throws { let myModel = MyModel() try myModel.doSomething2() } • #expect(throws: Never.self) という書き方も可能 ‣ エラーが throw されても処理を続行したい場合 エラーのテスト 26 / 28
まとめ
まとめ • Swift Testing の構成要素 ‣ テスト関数、期待値の確認、テストスイート、トレイト • Swift Testing の機能として、エラーのテストを紹介 ‣ 型やインスタンスの一致を確認 ‣ 確認処理のカスタマイズ まとめ 28 / 28