ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
テストの繰り返しによる信頼性の低いコードの診断
テストの繰り返しは、最も信頼性の低いコードのデバッグにも役立ちます。テストプラン、Xcode、xcodebuild内の最大反復テスト、失敗するまでのテスト、失敗した場合の再試行などを使用して、バグやクラッシャを追跡し、Appを誰にとってもより安定したものにする方法を確認しましょう。 このセッションを最大限に活かしていただくためには、XCTestとテストプランによるテストの管理に精通していることが推奨されます。詳しくは、WWDC19の「Xcodeでテストする」をご確認ください。
リソース
関連ビデオ
WWDC22
WWDC21
-
ダウンロード
こんにちは WWDC 2021 へようこそ 私は XcodeのXCTest 担当のSuzyです このセッションではテストを 繰返す為のツールである テストリピートを使って 信頼性の低いコードの 診断方法について学びます
App起動テストを実行する 過程で信頼性のない コードを実行するとテストが 失敗することがあります
レースコンディション 環境の仮定 グローバルステート 外部サービス通信等を扱う際 このような不整合が 発生することがあります これらのバグは 再現が難しいため 追跡するのが難しいバグです このような不具合を 診断する方法の1つは テストを繰返し 実行することです Xcode 13で追加された テストの繰返しでは 停止条件を指定して 指定した回数まで テストを繰りす事ができます Xcodeは3つのテスト繰返し モードをサポートしています 1つ目のモードは Fixed iterations です テストの状態に関わらず 一定の回数だけ テストを繰返します Fixed iterationsは テストの信頼性を把握し 時間の経過と共に新しい テストが導入されても 信頼性を維持するのに 役立ちます 2つ目は Until failure です Until failureは 失敗するまで テストを繰返します 私はこのツールを使って テストの失敗を再現し デバッガで 検出するのが好きです 最後は Retry on failure です これは 指定された最大値まで 成功するまで テストを再試行します 最初失敗しても再試行で 最終的に成功する様な 信頼性の低いテストを 特定するのに便利です CIのテストでこのような 挙動が見られる場合 テスト計画で一時的に Retry on failure を有効にして 問題を解決するための 追加データを 収集することができます 失敗時の再試行には 実際の製品の問題が 隠されてる可能性がある事を 覚えておく必要があります 最初に失敗してから 最終的に成功する 機能もあるので このモードを一時的に使用し 失敗を診断するのが最善です 実際にどう機能するのか 理解を深めていきましょう 「IceCreamTruckCountdown」 という アイスクリームトラックが 家の前を通るまでの時間を 教えてくれる Appを作りました クッキー&クリーム があると嬉しいので 全てのフレーバーが 揃っているかを確認する為に testFlavorsという テストを書きました testFlavors は truckDepot から 取得したトラックがあります
私はprepareFlavorsを 呼び出し 最後に 33種類のフレーバーが全て 揃っている事を表明します
最近 Xcode Cloud のメイン ブランチで testFlavors が 時々失敗することに 気づきました もっと情報を集めるために テストプランを 一時的に設定し Test Repetition Modeを失敗時に 再試行するようにしました レポートナビゲータを見て クラウドのレポートを チェックしてみましょう
テストが一貫して 失敗しているので 最後のテストをチェックして 情報を集めましょう
最初のデバイスを開くと イテレーションのラベル があり このテストの 最初のイテレーション であることがわかります
そして 他の行を すべて展開すると アサーションの失敗は すべて同じで この 最後のテストは 合格しています 1台のデバイスだけでなく すべてのテストが 一貫してパスすることを 期待していました この失敗をローカルで 再現してみましょう testFlavorsがある ファイルに行ってみましょう
Controlキーを押し テスト用の ダイヤモンドをクリック Run "testFlavors()" Repeatedly をメニューから選択し テストの繰返し ダイアログを表示します ここでは 繰返しの 停止条件を選択したり 最大反復回数を設定したり Pause on Failure等の オプションを 設定することができます ここでは クラウドレポートで 発生した問題を 再現したいので 停止条件を最大反復回数を 経るように設定し 100回にしておきます
では テストを実行してみます
よし! テストは ローカルで失敗しました 失敗のアノテーションを クリックすると Xcode Cloudで起こったのと 同じエラーが表示され 100回中4回失敗しました これで この問題をデバッグ できる様になりました もう一度 Controlキーを押しながら testFlavorsのテスト ダイヤをクリックし Run "testFlavors() " Repeatedly を選択 しかし 今回は失敗した時に 停止するようにして 問題が起きた時に デバッグできるようにします Xcodeは自動的にPause on Failure を選択してくれるので デバッガでエラーを検出 することができます
これで問題が解決しました トラックのフレーバーに 不整合があると わかっているので デバッガで トラックオブジェクト を見てみます
既に prepareFlavors を呼び出して いるので flavorsが33で あるべき所が0になって いるのはおかしいですね この completionHandler の中に 入ってしまったのでしょうか ブレークポイントを追加して 「続行」をクリックします
うーん なんか間違ってる気がします
fruffleは内側のprepareFlavors completionHandlerではなく 外側のcompletionHandlerで 呼び出されています
複数のcompletionHandler がある非同期イベントで 期待値が正しい場所で 満たされていない事が原因の かなり単純なバグです XCTestはSwift 5.5に対応しており 5async/await使用で テストを単純化し 二度と 起こらない様にできます このテストをasync/awaitを 使うように変換するには メソッドのヘッダーに async throws を追加します
iceCreamTruckを truckDepotから 取得するのに "await " バージョンを使用します
prepareFlavorsの "await " バージョンを使用します
同じアサートですがトラック はオプションではないです
このテストを もう一回実行して 修正されていることを 確認してみましょう Controlキーを押しながら Run "testFlavors()" Repeatedlyを選択し もう一度 Maximum Repetitionsを 停止条件として選択します
やったー! このテストは 100回パスしました これで解決したと 確信したので テスト計画から 失敗時の再試行を削除して 変更をコミットする 準備ができました という訳で デスク上の使い方と テスト計画に設定することで CIでテストを 繰返し実行する方法の 1つを理解したところです それでは デモのようにCLIを使って テストを繰返し実行する 別の方法を説明します xcodebuildを 直接実行する場合 テストプランの設定を 上書きする xcodebuildフラグを 追加することができます
test-iterationsに 数字を渡すと 固定回数のテストを実行でき また -retry-tests-on-failure や -run-tests-until-failure と 組み合わせると 他の停止条件と一緒に 使うことができます xcodebuildを使って同じ様に テストを実行するには テストの実行に使う基本の xcodebuildコマンドから始めて -test-iterationsを 100に設定し -run-tests-until-failureの フラグを追加します 要約すると テストの繰返しは 信頼性の低いコードを 診断するための ツールとして使用します 一貫性のないテストの 取り扱いについては 「XCTestで想定される失敗の容認」 をご覧ください Swiftのasync については 「Swiftのasync/awaitについて」 をご覧ください ご覧いただき ありがとうございます [音楽]
-
-
2:39 - testFlavors
func testFlavors() { var truck: IceCreamTruck? let flavorsExpectation = XCTestExpectation(description: "Get ice cream truck's flavors") truckDepot.iceCreamTruck { newTruck in truck = newTruck newTruck.prepareFlavors { error in XCTAssertNil(error) } flavorsExpectation.fulfill() } wait(for: [flavorsExpectation], timeout: 5) XCTAssertEqual(truck?.flavors, 33) }
-
6:31 - testFlavors: add async throws to method header
func testFlavors() async throws { var truck: IceCreamTruck? let flavorsExpectation = XCTestExpectation(description: "Get ice cream truck's flavors") truckDepot.iceCreamTruck { newTruck in truck = newTruck newTruck.prepareFlavors { error in XCTAssertNil(error) } flavorsExpectation.fulfill() } wait(for: [flavorsExpectation], timeout: 5) XCTAssertEqual(truck?.flavors, 33) }
-
6:32 - testFlavors: use the async version of the ice cream truck
func testFlavors() async throws { let truck = await truckDepot.iceCreamTruck() truck = newTruck newTruck.prepareFlavors { error in XCTAssertNil(error) } flavorsExpectation.fulfill() } wait(for: [flavorsExpectation], timeout: 5) XCTAssertEqual(truck?.flavors, 33) }
-
6:33 - testFlavors: use the async version of prepareFlavors
func testFlavors() async throws { let truck = await truckDepot.iceCreamTruck() try await truck.prepareFlavors() XCTAssertEqual(truck?.flavors, 33) }
-
6:50 - testFlavors: the truck is no longer optional
func testFlavors() async throws { let truck = await truckDepot.iceCreamTruck() try await truck.prepareFlavors() XCTAssertEqual(truck.flavors, 33) }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。