ハイライト
Xcode 13 では、XCTest にテストの繰り返し (Test Repetition) が追加されます。開発者は、固定回数、失敗するまで、および失敗の再試行の 3 つのモードを使用して不安定なテストを再現し、Xcode、テスト計画、および xcodebuild を使用して診断データを収集できます。
主要内容
時折発生するテストの失敗に対処するのが最も困難です。
同じテストが今日ローカル マシンでは成功しますが、明日の CI では失敗します。もう一度クリックしてもう一度通過します。問題は、競合状態、環境の仮定、グローバル状態、または外部サービスの通信から発生する可能性があります。開発者には安定した再現パスがなく、失敗ログを 1 回確認して、次にどこを確認するかを推測することしかできません。
Xcode 13 でテストを繰り返し実行すると、このエントリの問題が解決されます。指定された回数テストを実行し、各実行に停止条件を追加します。失敗は一度だけではなくなりました。これを押して、障害箇所で停止し、デバッガを起動してオブジェクトのステータスを確認します。
セッションの例としては、IceCreamTruckCountdown アプリがあります。テストtestFlavorsからtruckDepotアイスクリームトラックを手に入れるには、電話してくださいprepareFlavors、最終的には33種類のフレーバーがあると主張しています。このテストは、Xcode Cloud の master ブランチで散発的に失敗します。失敗の再試行が有効になった後、レポートには最初のいくつかの失敗と最後のパスが表示されます。これは、問題が再発するが、その可能性は高くないことを意味します。
次に、開発者は同じテストをローカルで 100 回実行します。テストは4回失敗しました。次に、停止条件を障害までに変更し、障害一時停止を有効にします。 Xcode は失敗すると停止します。デバッガで見るflavorsまだ0、その理由はXCTestExpectation外側の完了ハンドラーで完了し、内側の完了ハンドラーでprepareFlavorsまだ終わっていないのです。
修正方法は、テストを Swift 5.5 の async/await に変更することです。非同期処理はシーケンシャルコードになり、prepareFlavorsアサーションは完了後にのみ入力してください。 100 回繰り返してすべて合格した後、開発者は失敗した再試行構成をテスト計画から削除します。
詳細
テストを繰り返し実行する 3 つのモード
(00:56) Xcode 13 でのテストの繰り返し実行では、最大反復回数と停止条件を設定できます。セッションには、固定反復、失敗するまで、および失敗時の再試行という 3 つのモードが導入されています。
# 固定次数:运行指定次数,结果不会提前停止
xcodebuild test -test-iterations 100
# 失败重试:失败后重试,直到成功或达到最大次数
xcodebuild test -test-iterations 10 -retry-tests-on-failure
# 直到失败:持续运行,遇到第一次失败就停止
xcodebuild test -test-iterations 100 -run-tests-until-failure
キーポイント:
xcodebuild testテスト アクションを実行します。-test-iterations 100最大 100 回の実行を指定します。-retry-tests-on-failure失敗したテストを再試行して、CI の不安定なテストに関するデータを一時的に収集します。-run-tests-until-failure障害が発生した場合は停止します。非決定的な障害をデバッガーに取り込むのに適しています。
(01:10) テストスイートの信頼性を観察するには、固定回数が適しています。途中で障害が発生しても、何度でも実行されます。チームはこれを使用して、新しいテストが追加された後にスイートが散発的に失敗し始めるかどうかを確認できます。
xcodebuild test -test-iterations 100
キーポイント:
- このコマンドは反復回数のみを設定します。
- 追加の停止条件がないため、すべての反復が実行されます。 ・出力結果は、100回中4回などの故障率の計算に利用できます。
(01:28) 失敗するまではローカルでの再生に適しています。最初に失敗するまでテストを実行し続けます。 Xcode の Pause on Failure を使用すると、失敗した箇所で停止できます。
xcodebuild test -test-iterations 100 -run-tests-until-failure
キーポイント:
-test-iterations 100繰り返し実行する際の上限を設定します。-run-tests-until-failure停止条件を失敗時に停止するように変更します。- ローカル デバッグ中に、開発者は最初にこのモードを使用して、時折発生する障害をチェック可能な障害サイトに変換できます。
(01:41) 失敗した再試行は、一時的な診断 CI に適しています。テストが失敗した後は、テストが成功するか最大回数に達するまで再試行されます。セッションは、失敗した再試行には製品の実際の問題が隠れている可能性があるため、診断の完了後に削除する必要があることを明示的に通知します。
xcodebuild test -test-iterations 10 -retry-tests-on-failure
キーポイント:
-retry-tests-on-failure失敗した場合にのみ再試行してください。- 最終的な成功は、最初の実行で問題がないことを意味するものではありません。 ・このモードは故障データの収集には適していますが、長期間にわたる故障の隠蔽には適していません。
完了ハンドラーによって引き起こされる競合状態
(02:39)testFlavors使用したオリジナルバージョンXCTestExpectation非同期コールバックを待ちます。コードはアイスクリームトラックがフレーバーを準備するのを待っているように見えますが、実際の待機ポイントは間違った場所にあります。
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)
}
キーポイント:
var truck: IceCreamTruck?非同期コールバックで値が割り当てられるため、最初に car をオプションの値として宣言します。XCTestExpectationテストを非同期操作を待機させるために使用されます。truckDepot.iceCreamTruck { newTruck in ... }アイスクリームトラックを非同期で取得します。newTruck.prepareFlavors { error in ... }フレーバーの準備を非同期的に続行します。flavorsExpectation.fulfill()内部コールバックよりも前に、外部コールバックに配置します。prepareFlavors仕上げる。wait(for:timeout:)フレーバーの準備が完了するのではなく、期待が完了するのを待つだけです。XCTAssertEqual(truck?.flavors, 33)入っているかもしれないflavors0のまま実行されます。
(05:30) Xcode が失敗時に一時停止した後、デバッガーは次のように表示します。flavorsブレークポイントのチェックを続けた結果、内部の完了ハンドラーがまだ実行されていないことがわかりました。根本原因は待機位置にあります。テストが待機を途中で終了し、アサーションが早期に実行されます。
newTruck.prepareFlavors { error in
XCTAssertNil(error)
}
flavorsExpectation.fulfill()
キーポイント:
prepareFlavors完了ハンドラーは、フレーバーの準備が完了したときを表します。fulfill()完了ハンドラーの外部で実行されます。- テストで非同期プロセスが完了したとみなされるとき、実際の業務操作はまだ実行中です。
- このタイプのエラーは、ネストされた完了ハンドラーでは簡単に無視されます。
async/await を使用してテストを書き直す
(06:18) XCTest は Swift 5.5 で async/await をサポートします。テストメソッドは次のように宣言できます。async throwsをクリックし、非同期 API が完了するまで直接待ちます。
func testFlavors() async throws {
let truck = await truckDepot.iceCreamTruck()
try await truck.prepareFlavors()
XCTAssertEqual(truck.flavors, 33)
}
キーポイント:
async throwsテスト メソッドが非同期 API を呼び出し、エラーを XCTest に渡して処理できるようにします。await truckDepot.iceCreamTruck()アイスクリームトラックの帰りを待っています。let truckコードは実行を続行する前に戻るまで待機するため、これはオプションではなくなりました。try await truck.prepareFlavors()フレーバーの準備ができるまで待ち、スローされる可能性のあるエラーを処理します。XCTAssertEqual(truck.flavors, 33)両方の非同期ステップが完了した後に実行されます。
(06:31) セッションは書き換えプロセスの最初のステップを示しています: 最初に追加async throws。このステップではメソッドのシグネチャのみが変更されます。await電話してポータルを開きます。
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)
}
キーポイント:
asyncin vivoでの使用が許可された試験await。throwsin vivoでの使用が許可された試験try await。- 現時点では、テスト ロジックはまだ修正されておらず、非同期呼び出しを書き換えるための準備にすぎません。
- 期待ハンドラーと完了ハンドラーは後続のステップで削除されます。
(06:40) 次に、非同期バージョンを使用しますiceCreamTruck、その後、非同期バージョンを使用しますprepareFlavors。最終的なコードは、元のアサーション ターゲットを保持しますが、手動待機オブジェクトを削除します。
func testFlavors() async throws {
let truck = await truckDepot.iceCreamTruck()
try await truck.prepareFlavors()
XCTAssertEqual(truck.flavors, 33)
}
キーポイント:
- 関数シグネチャの最初の行は、これが非同期テストであることを示します。
- 2列目の待機中
truckDepot戻るIceCreamTruck。 - 3列目は車がフレーバーの準備を終えるのを待ちます。
- 4 行目は、フレーバーの数が 33 に等しいかどうかをチェックします。
- コードシーケンスはビジネスシーケンスと一致していますが、これは間違っています
fulfill()場所が消えてしまいます。
Xcode および Xcode Cloud で反復結果を表示する
(03:00) セッション まず、テスト計画で失敗時の再試行を一時的に有効にしてから、レポートナビゲーターに移動して Xcode Cloud レポートを表示します。レポート内の反復ラベルは、各実行のシーケンス番号を示します。開発者がレコードを展開すると、アサーションが同じように失敗し、最後のアサーションが成功したことがわかります。
xcodebuild test -test-iterations 10 -retry-tests-on-failure
キーポイント:
- このコマンドは、テスト計画の失敗リトライのアイデアに対応します。
- CI レポートには、複数回の反復からの結果が保持されます。 ・ 障害情報が複数回同じ場合は、同じアサーションに沿って確認を継続できることを意味します。
- 最後のパスは、再試行が成功したことを意味するだけで、問題が解消されたことを意味するものではありません。
(04:03) ローカルでは、開発者はテストのひし形アイコンを Control キーを押しながらクリックして、Run "testFlavors()" Repeatedly…。ダイアログボックスでは、停止条件、最大繰り返し回数、失敗時の一時停止を設定できます。
xcodebuild test -test-iterations 100 -run-tests-until-failure
キーポイント:
- Xcode のグラフィカル インターフェイスは、単一のテストのアドホックな再現に適しています。
xcodebuildスクリプトおよび CI のテスト計画設定をオーバーライドするのに適しています。- セッションの例では、最大回数が 100 回に設定されており、ローカルで 4 回の失敗が発生します。
- 2 回目の実行で Pause on Failure を選択すると、Xcode は自動的に Pause on Failure をチェックし、デバッガーへの入力が容易になります。
重要ポイント
-
何をすべきか: 「失敗した 1 つのテストを繰り返す」診断タスクを CI に追加します。 実行する価値がある理由: セッションでは、失敗の再試行を使用して Xcode Cloud で不安定なテスト データを収集する方法を示します。 開始方法: CI スクリプトにターゲット テストを追加します
-test-iterations、シーンで選ぶ-retry-tests-on-failureまたは-run-tests-until-failure。 -
何をすべきか: 時々失敗するテストの失敗率を傾向として記録します。 実行する価値がある理由: 固定時間モードでは、テスト スイートの信頼性を測定でき、新しいテストを追加した後の安定性の低下を検出するのが簡単です。 開始方法: 定期的に実行する
xcodebuild test -test-iterations 100、パスの数、失敗の数、および失敗したアサーションを記録します。 -
内容: プロジェクト内のネストされた完了ハンドラーを使用して XCTest を監査します。 実行する価値がある理由: セッションのバグは、間違った完了ハンドラーで完了することが期待されることから発生します。 開始方法: 検索
XCTestExpectation、wait(for:timeout:)確認のためのマルチレイヤーコールバックfulfill()実際に業務を遂行する立場に置かれているかどうか。 -
やるべきこと: 非同期 XCTest を async/await に移行します。 実行する価値がある理由: XCTest のサポート
async throwsテスト メソッドと非同期ステップは、業務上の順序で記述できます。 開始方法: まず、テスト方法を次のように変更します。async throws、利用可能な非同期 API を次のものに置き換えます。awaitまたはtry await電話。 -
やるべきこと: ローカル デバッグ用に「失敗時に停止」の習慣を確立します。 実行する価値がある理由: Till-to-fail モードでは、確率の低い失敗をデバッガーに取り込むことができるため、ログを確認するだけの推測作業が軽減されます。 開始方法: Xcode でテストのひし形アイコンを Control キーを押しながらクリックし、選択します
Run Repeatedly…、停止条件を「失敗するまで」に設定し、「失敗時に一時停止」を有効にします。
関連セッション
- XCTest で予想される失敗を受け入れる — 使用します
XCTExpectFailureテストレポートのノイズを減らすために既知の問題をマークします。 - Swift での async/await について — 理解する
async、await完了ハンドラーから移行する基本的な方法。 - Xcode Cloud ワークフローを探索する — Xcode Cloud でビルド、テスト、配布プロセスを構成します。
- Digital Crown、Trackpad、iPad ポインターの自動化を探索する - Xcode 13 の新しい入力自動化機能で UI テストの範囲を拡大します。
- ブレークポイントの改善点を確認する - ブレークポイントと LLDB を使用して、実行時に障害シナリオを検査します。
コメント
GitHub Issues · utterances