ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Xcode Cloud用の高速で信頼性の高いテストを作成する
Appleの継続的インテグレーションおよび継続的デリバリサービスであるXcode Cloud向けに、効果的なテスト計画を作成する方法をご覧ください。ご利用のコードが正常に機能することを継続的に検証する上で、テストの実施がどれほど重要になるのか解説します。さらに、Xcode Cloud向けに高速で信頼性が高く、効率的なテストを作成したり、無駄な失敗を回避したり、コードの変更を迅速に検証したりする方法も紹介します。
リソース
関連ビデオ
WWDC23
WWDC22
WWDC21
WWDC20
WWDC19
WWDC18
-
ダウンロード
こんにちは「Xcode Cloud用の高速で 信頼性の高いテストを作成する」にようこそ 私はXCTestで働くSuzyです このセッションでは Xcode Cloudテストを始める為 最も効果的な方法を紹介します 私のチームは すべての開発者のため 強力なツール Xcode Cloudを設計しました Xcode自体のテストにも 使い とても気に入っています Xcode Cloudのお気に入りの機能の1つは 与えられたテスト スイートを 大幅に拡張する機能です
ほとんどのテストを クラウド上で実行する様に構成し iPhone iPad Apple Watch Apple TV Macなど多様な プラットフォームを活用するため OSのバージョンが異なる 複数のテスト先でテストを実行したり アドレスサニタイズや スレッドサニタイズなどの 実行時分析ツールを使ってさまざまな テストプラン構成を実行することが 現実的な方法となります このような徹底的な テストスイートをクリアすれば 自信を持って出荷できるコードになります Xcode Cloudにテストをオフロードし 開発者コード コンパイル テストといった デスクトップサイクルに影響を与えず より広範なテストセットを実行できます このようにテストスイートを拡張したことで 信頼性の低いテストが増える可能性があります このような状況は 手に負えなくなる可能性があります そのため信頼性の確保は必須です 信頼性に加え 大量のテストは 継続的インテグレーション プロセスへの影響を抑えるために 効率的に実行する必要があります まず信頼性から取り上げましょう Food Truckを使い Xcode Cloudの より信頼性の高いテストを オーサリングする方法を紹介します Food Truckは タップやスワイプを ドーナツに変換するAppです Xcode Cloudでテストスイートを実行し 全Appleプラットフォームで私の好きな ドーナツスプリンクルチョコレートの 注文が可能かを検証できます Xcode Cloud Workflowの各改善点を洗い出し デモンストレーションを行います Xcode Cloud Workflowsを始める詳細については 「Xcode Cloudの紹介」をご覧ください
より信頼性の高いテストを 作成するための第一歩は 各テストの設定と ティアダウンを徹底することです Xcode Cloud でのテストでは 開発者の当初の想定を 満たさない可能性のある 新シミュレータを使用します テストコードで時々見られる デバイス構成の仮定を いくつか挙げてみましょう 一部のテストでは 特定の日時に依存します サーバが異なるタイムゾーンで 動作している場合です テストはタイムゾーンに 依存しない必要があります 数字の書式や言語の方向性など ロケールに基づく値は 予期せぬ結果をもたらすことがあります シミュレータのロケールを 明示的に設定し 回避できます インターネットへのアクセスなど 特定のデバイスの権限に 依存することも問題視されています ユニットテストでは デバイスのパーミッションをモック UIテストではアラートハンドラ を使用するのがベストです 最後に 一部のテストは プリロードデータに依存します テストは空の書類ディレクトリ を期待するかもしれません シミュレータを明示的に設定することが最も簡単な選択である場合もありますが 一般的にはテスト設定方法を 強化することがより強固です 例えば Food Truck は メニューファイルに依存します setup関数でtruckオブジェクト をインスタンス化する際に ドーナツメニューが含まれる モックデータファイルを生成します ティアダウンメソッドに頼って 後続のテストの準備をするより 設定メソッドで全状態準備を 確立することをお勧めします 読み取り専用のファイルを リポジトリにチェックインし 後でテストでアクセスできます これらのファイルを構築する必要がある場合 Xcode Cloud は 複数のテストがアクセスできる様 ファイルを一度生成できる カスタム設計 スクリプトを支援します スクリプトの設定方法の詳細については 「高度なXcode Cloudワークフローの カスタマイズ」をご覧ください
以上でシミュレータの設定は完了です 前提条件を満たせなかった テストの処理方法を説明します XCTSkipは XCTest Runnerに 現在のテスト実行を中止させ スキップとしてマークするよう 指示するエラーです これは まだ未サポートのOS版や デバイスの種類迂回に使用されます また XCTSkip を利用し環境変数を設定 ステージング環境や本番環境に 特有のテストのスキップもできます 環境変数を使い テストの流れを 制御する方法を説明します
環境変数はデバイス上の XCTestテストランナーAppと xcodebuildを実行しているテストホストの 両方にパラメータを提供できます Xcode Cloudでは TEST_RUNNER_ を先頭に持つ環境変数が XCTestのテストランナーに渡されます このプレフィックスは変数がコードから 利用できるようになる前に取り除かれます 例えば テストコードにある BASE_URLという変数は TEST_RUNNER_BASE_URLという環境変数として 渡されることになります テスト計画には テストコードと 同じフォーマットが必要です すなわち TEST_RUNNER_という 接頭辞は付けません 環境変数は テストコードの どこからでも参照できます 例えば XCTSkipと一緒に使うことで 本番環境で実際にドーナツを注文する テストをスキップできます もちろん お腹が空いていれば別ですが テスト計画とXcode Cloud User Interfaceの両方など 複数の場所で環境変数を 再定義すると 予期しない結果の 可能性があることに 留意することが重要です この場合Xcode Cloudの環境変数が プロジェクトのテスト計画で 指定したものより優先されます テストコード内で環境変数を参照しているため Xcode Cloudユーザ インターフェイスで その値を設定できます
これには Cloud Reportsに移動し Controlキーを押しながら Food Truckをクリックします
ワークフロー内の環境変数を編集するために コンテキストメニューから 「Manage Workflows」を選択します 統合ワークフローを具体的に編集しているので ダブルクリックします ここで サイドバーの「Environment」を選択し シート中央の「Environment Variables」に 変数の名前と値を追加できます
Xcode Cloud Workflowで 環境変数を設定する代わり テスト計画内で 設定することができます この例では まだテストプランがありません テスト計画を有効にするには スキームエディタを開き サイドバーで「Test」を選択 「Convert to Use Test Plans」をクリックします
「Food Truck」と名付けた テストプランができました 環境変数を追加するには テスト計画をクリックし エディタを開く必要があります
上部付近では「Tests」と 「Configurations」を選択できます 「Configurations」を選択してみましょう では「Arguments」セクション内で 「Enbironment Variables」をクリックし 変数を追加します ポップアップが表示されるので 変数の名前と値を入力します
これで本番環境ではテストがスキップされます テストのスキップについて詳しくは 「テストをXCTSkipする」をご覧ください
XCTSkipを制御する為の 環境変数利用をカバーしたので 期待値タイムアウトについて話しましょう 予期せぬタイムアウトにより テストが失敗する事があります 例えば 遅いサーバユーザーインターフェイスの テストに過剰な心配をした 結果である可能性があります どちらの問題も解決する一つの方法は XCTestExpectationのタイムアウトを長くし 対話が終了するのに 十分な時間を確保することです この例では OrderDonutの タイムアウトを10秒に増やし サーバが応答する時間をより長くしています 通常 Appとテストコードのタイムアウト処理を async/awaitへの置き換えが望ましいです この方法ではawait呼び出しが終了するまで タイムアウトなしでテストを一時停止ができます
時間依存のテストは解決したので テストスイート内でテストの 失敗を処理しましょう 例えば メンテナンスのため 停止しているステージング環境内の サービスに依存するテストがあるとします これを無効や スキップする代わりに XCTExpectFailure を使えます XCTExpectFailure を使うと テストは通常通り実行され 結果は次のように変換されます: テストの失敗は予想される失敗として報告され そのスイート内の失敗した テストは合格として報告されます 予想される失敗で発生する ノイズを除去できます
例えば testOrderDonutは失敗しています ドーナツの注文を提供するサービスが 今メンテナンス中なのは知っているので ここにXCTExpectFailureの 呼び出しを追加しました XCTExpectFailureの詳細については 「XCTestで想定される失敗の容認」を ご覧ください
予想される失敗を宣言したので コードの実証と信頼性のないコードの診断に テストの繰り返しを活用してみましょう テストの繰り返しは最初の失敗 最初の成功 または統計的な結果を待って 同じテストを複数回実行するツールです 例えば 私たちのデスクでは 新しいコードとテストケースを 繰り返し複数回実行し 初期Appとテストコードの信頼性を 確認してからチェックインしています 結果 testOrderDonutの成功率は80%のみでした 大変です! 失敗の存在を知っているので repeat-until-failureモードで 局所的にバグを診断します これもテスト繰り返しの活用法です 信頼性の低い外部サービスに 依存するテストでは retry-on-failureの繰り返しポリシーを活用し テストが成功することを 確認したい場合があります テストの再試行は強力なアプローチですが 可能な限り外部サービスの モックを作る事が望ましいです モックサービスの利点は 決定論的な信頼性と速度です 依存関係をモック化する方法については 「テストのヒントとコツ」をご覧ください テストの繰り返しを 有効にする方法を見ましょう
テスト計画でテストの 繰り返しを有効にするには テスト計画エディタに戻り 「Configurations」を選びます
次に「Test Execution」セクションの下に テストの繰り返しモードの 選択ポップアップがあります
今回は 主に信頼性の低い 外部サービスを回避するのに 使用される「Retry on Failure」を選択します これで テスト繰り返しモードが 有効になりました テスト繰り返しの活用につきましては 「テスト繰り返しによる信頼性の低い コードの診断」をご覧ください そこで テストの信頼性を 向上させるために使える さまざまなツールについて解説してきました 品質テストの書き方については 「不具合現出テストを書く」をご覧ください テストが信頼できるので次は高速な動作です! より高速な結果を得る 設定オプションが多数存在します テストスイートの実行に かかる時間の短縮をしてみましょう
パフォーマンスを向上させる手法として テストを複数のテストプランに 分割することがあります 時には 2つで十分なこともあります プルリクエストをオープンや アップデートするたびに 検証する縮小された テストセットを特定できます
例えば あるプラットフォームに対して ユニットとユーザインターフェーステストの 重要なサブセットを実行できます
サポートされているすべてのプラットフォームでのフルセットのテストは引き続き実行できますが 現在はバックグラウンドで実行され プルリクエストのブロックはありません
継続的インテグレーション プロセスをタイムリに維持しながら テストや新プラットフォームを 追加することができます
選択したテストセット実行の ワークフローを設定しましょう この例では すでに 「Pull Requests」という新しい テスト計画を作成しエディタで開いています 上部の「Tests」と「Configurations」 のどちらかを選択できます 「Tests」を選択してみましょう
プルリクエストの為に検証する テストのサブセットを選択しました プルリクエストテスト計画を 実行するワークフローの設定は テストをスキップする環境変数を 追加したときと同じように Xcode Cloud Manage Workflowsに戻りましょう ワークフローを新規に作成する為 「Manage Workflow」シートの 左下にある「Add」ボタンをクリックします 簡単に ワークフローの名前も 「Pull Requests」とし 開始条件を選択しましょう テストが失敗している状態で チェックインを行わなくします サイドバーの「Start Conditions」右側にある 「Add」ボタンをクリックします
開始条件のオプションが 表示されたメニューが表れます 今回は「Pull Request Changes」を選択します
これでプルリクエストの開始条件が整いました テスト実行には まず Food TruckAppをビルドします その為には ビルドアクション を追加する必要があります 再びサイドバーの「Start Conditions」の下に アクションを追加しましょう 「Actions」の隣にある 「Add」ボタンをクリックし コンテキストメニューから 「Build」を選択します
Appをビルドするアクションができたので テスト実行のアクションを もうひとつ追加します アクション追加を再度クリックし 今回は「Test」を選択します
素晴らしい テストアクションができましたね 実行するテストプランを選択しましょう シートの真ん中に テスト用 ドロップダウンがあります ここでは「Pull Requests」 テストプランを選択できます
素晴らしい! プルリクエストにテストプランを 実行するワークフローが設定されました テスト全体を予定に沿って実行する 2つ目のワークフローを作るには 同様の手順を実行します 今回は開始条件を 「On a Schedule for a Branch」とし フルスイートのテストプランを 実行するようにワークフローを設定します 両方のワークフローがXcode Cloud で構成され 関連するテストプランを実行しています テスト計画を更に知りたい方は 「Xcodeでテストする」をご覧ください
プルリクエストを作りワークフロー のテストセットを予定しました テストの同時進行も スピードアップの改善点です デフォルトではXcode Cloudは プラットフォームを並行しテストします ターゲットとテスト目標のクラスレベルで Xcodeがテストを並行して 実行できるようにできます
Xcodeでテストの並列実行を可能にするため 再びテストプランエディタで “Tests “を選択します
「Food Trucks Tests」テストの束の右側にある 「Options」ボタンをクリックします
オプションの1つで可能な限り 「Execute in parallel」事ができます サーバーに十分なコアがあれば 複数ターゲットとテスト目標 クラスを同時に実行できます これを有効にし テストスイートの 変更時間を向上させましょう
テストが並列に実行されるよう設定されました 並列実行の利点を生かすには テストが独立して実行される様 設計されなければなりません 適切な設定とティアダウンは 信頼性の高いテスト動作に不可欠です
テストが並行して行われるようになったので 次は暴走テストに目を向けましょう 暴走テストとは タイムリに 終了しないテストのことです 例としては 無限ループや故障したサーバーを 無限に待ち続けることなどがあります
テストプランに実行時間の許容値を設定し これらのテストを停止できます 実行時間の許容値は テストがタイムアウトエラーで 失敗するまでの実行秒数を指定します テストスイートが個々のテストで 行き詰まるのを防げます
この場合 5回目のテストが 何かの理由で引っかかりました 実行時間に余裕を持たせることで この暴走は最終的に停止し失敗と判定された XCTest Test Runnerは 次のテストの実行を継続しました テスト計画の実行時間の 許容値を設定しましょう
実行時間の許容値を設定するために テストプランエディタで 「Configurations」を選択します
「Test Execution」カテゴリで 「Test Timeouts」を有効にし 待ち時間を秒単位で指定することができます なお デフォルトは600秒です
最大実行時間の許容値を設定したことで 1つのテストの暴走が ワークフローを乱さなくなりました 例えば 夜間に行われる テストが時間通りに完了し 有用な結果を提供できる様になりました やった! 暴走していたテストを止められたので 次の改善に進むことができます 外部サービスに依存する テストの信頼性を高めるため テストの繰り返しを活用できました 失敗時に再試行するようにテスト計画を設定し 十分な繰り返し値を選択しました しかし この繰り返しはテストの 実行時間を増やす可能性があります
不要な繰り返しは無駄なので テストの繰り返し値を低い数値に最適化します 問題のテストをプルリクエスト のワークフローから 完全な削除を検討してもよいでしょう では どうすればよいのでしょうか
テストプランエディタのテスト repetitionsNconfigurationに戻ります
テストの繰り返しモードを 「Retry on Failure」に設定しました ここで「Maximum Test Repetitions」 の値を調整します 例えば 5%の確率で失敗する 外部サーバ依存テストでは 最大10回の試行を 許可したかもしれません ほとんどの場合 1回目の挑戦で成功します しかし その同じテストに 関係のないバグがあると 毎回失敗し 10回すべて 使ってしまうことになります もしかしたら 3回の試行で十分で より良い選択かもしれません
パフォーマンス向上の為に 再試行回数を減らしたいですが 信頼性の向上のために 再試行回数を増やすことを 推奨したケースもあることに注意しましょう その為 この最小限の値は テストを確実に実行するのに 十分であり続ける必要があります 以上で 高速化のための設定は終了です より早くテスト結果を得るための詳細は 「より早くテスト結果を得る」をご覧ください
要約するとXcode Cloudのテストを開始する 最も効果的な方法について説明しました 無関係な失敗を避け コード変更を迅速に検証できる様 信頼性と高速性の両方を備えた テスト構成に注力しました ありがとうございました! 残りのWWDCも楽しんでください
-
-
3:37 - setUp()
override func setUp() async throws { }
-
3:46 - setUp() example
var truck: Truck! override func setUp() async throws { let directoryURL = FileManager.default.temporaryDirectory let fileName = UUID().uuidString let fileURL = directoryURL.appendingPathComponent(fileName, isDirectory: false) let data = await mockDonutMenuData() try data.write(to: fileURL) truck = Truck(menuURL: fileURL) }
-
5:55 - Environment variable example
var truck: Truck! func testOrderDonut() throws { let host = ProcessInfo.processInfo.environment["BASE_URL"] let expectation = XCTestExpectation(description: "Order donut") truck.order(with: .sprinkles, host: host) { error, donut in XCTAssertTrue(donut.hasSprinkles) expectation.fulfill() } wait(for: [expectation], timeout: 5) }
-
6:00 - XCTSkip example
var truck: Truck! func testOrderDonut() throws { let host = ProcessInfo.processInfo.environment["BASE_URL"] try XCTSkipIf(host == "prod.example.com") let expectation = XCTestExpectation(description: "Order donut") truck.order(with: .sprinkles, host: host) { error, donut in XCTAssertTrue(donut.hasSprinkles) expectation.fulfill() } wait(for: [expectation], timeout: 5) }
-
8:18 - XCTSkip example
var truck: Truck! func testOrderDonut() throws { let host = ProcessInfo.processInfo.environment["BASE_URL"] try XCTSkipIf(host == "prod.example.com") let expectation = XCTestExpectation(description: "Order donut") truck.order(with: .sprinkles, host: host) { error, donut in XCTAssertTrue(donut.hasSprinkles) expectation.fulfill() } wait(for: [expectation], timeout: 5) }
-
8:48 - XCTestExpectation example
var truck: Truck! func testOrderDonut() throws { let host = ProcessInfo.processInfo.environment["BASE_URL"] try XCTSkipIf(host == "prod.example.com") let expectation = XCTestExpectation(description: "Order donut") truck.order(with: .sprinkles, host: host) { error, donut in XCTAssertTrue(donut.hasSprinkles) expectation.fulfill() } wait(for: [expectation], timeout: 5) }
-
8:59 - Increase XCTestExpectation example
var truck: Truck! func testOrderDonut() throws { let host = ProcessInfo.processInfo.environment["BASE_URL"] try XCTSkipIf(host == "prod.example.com") let expectation = XCTestExpectation(description: "Order donut") truck.order(with: .sprinkles, host: host) { error, donut in XCTAssertTrue(donut.hasSprinkles) expectation.fulfill() } wait(for: [expectation], timeout: 10) }
-
9:16 - Async/await example
var truck: Truck! func testOrderDonut() async throws { let host = ProcessInfo.processInfo.environment["BASE_URL"] try XCTSkipIf(host == "prod.example.com") let donut = try await truck.orderDonut(with: .sprinkles, host: host) XCTAssertTrue(donut.hasSprinkles) }
-
10:02 - XCTExpectFailure example
var truck: Truck! func testOrderDonut() async throws { let host = ProcessInfo.processInfo.environment["BASE_URL"] try XCTSkipIf(host == "prod.example.com") let donut = try await truck.orderDonut(with: .sprinkles, host: host) XCTAssertTrue(donut.hasSprinkles) }
-
10:06 - XCTExpectFailure example
var truck: Truck! func testOrderDonut() async throws { let host = ProcessInfo.processInfo.environment["BASE_URL"] try XCTSkipIf(host == "prod.example.com") XCTExpectFailure("<https://dev.myco.com/bug/98> Donut ordering service is down") let donut = try await truck.orderDonut(with: .sprinkles, host: host) XCTAssertTrue(donut.hasSprinkles) }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。