WWDC Quick Look 💓 By SwiftGGTeam
Get your test results faster

Get your test results faster

元の動画を見る

ハイライト

Xcode 12 では、物理デバイスでの実行時間の許可と並列分散テストが XCTest に追加され、中断されたテストがスピンダンプを生成してスイートの残りの実行を継続できるようになります。また、xcodebuild がテスト クラスを複数の iOS または tvOS デバイスに分散して並列実行できるようになります。

主要内容

テストのフィードバック ループはシンプルです。テストを作成し、テストを実行し、レポートを読んで、コードの修正を続行するか、テストを追加するか、機能をリリースするかを決定します。問題は、ループが「テストの実行」ステップでスタックする可能性が最も高いことです。金曜日の仕事が終わる前に開始された CI は月曜日の朝には終了しませんでした。レポートには、タスクがキャンセルされたとだけ書かれており、チームは結果を取得できず、どのコードがテストをハングさせたのかもわかりませんでした。

以前、このような長期にわたる応答のない状況に遭遇したとき、私は推測することしかできませんでした。デッドロックであるか、メインスレッド CPU が過剰に動作しているか、タイムアウトの設定が広すぎる可能性があります。推測はテストのメンテナンスを考古学に変えます。 Xcode 12 の実行時間許容値は、各テストの最大実行時間を設定項目に変換します。タイムアウト後、Xcode は最初にスピンダンプを取得し、次に中断されたテストを終了し、テスト ランナーを再起動して、残りのテストの実行を継続できるようにします。

完全なレポートを取得した後の次の課題は速度です。 Fruita の例では、12 個のテストを連続して実行するのに 13 分近くかかります。消費時間は不均一で、数百ミリ秒かかる場合もあれば、数分かかる場合もあります。 Xcode 12 は並列分散テストを拡張します。xcodebuild はテスト クラスを複数の実行先に分散できます。各デバイスは 1 つのクラスの実行を終了した後、割り当てるテストがなくなるまで次のクラスを受信し続けます。

このセッションで説明する「高速」には明確な順序があります。まず、テストが無期限にスタックしないことを確認し、次にスピンダンプを使用して一時停止位置をポイントし、最後に並列分割に適したテストを分散します。このようにして、CI レポートは「結果なし」から「失敗、診断、および費やされた合計時間」に変わり、チームは結果に基づいて行動できるようになります。

詳細

1. 実行時間の許容値を使用して、中断されたテストを処理します。

(03:56) Xcode 12 の新しいテスト計画オプションは、実行時間許容値と呼ばれます。 Xcode をオンにすると、個々のテストの実行時間が制限されます。テストが制限を超えると、Xcode はスピンダンプを取得し、保留中のテストを強制終了し、テスト ランナーを再起動して、スイート内の他のテストの実行を継続します。

import XCTest
@testable import Fruta

class SmoothieNetworkingTests: XCTestCase {
    func testUpdatingSmoothiesFromServer() throws {
        let originalSmoothies = Smoothie.all
        try Smoothie.fetchSynchronouslyFromServer()
        XCTAssertNotEqual(originalSmoothies, Smoothie.all)
    }
}

キーポイント:

  • SmoothieNetworkingTests継承するXCTestCaseこれは、Xcode によって実行され、時間を測定されるテスト クラスです。 -testUpdatingSmoothiesFromServer()これは、セッション デモでハングするテスト メソッドです。 -let originalSmoothies = Smoothie.allネットワーク更新前のデータを記録します。 -try Smoothie.fetchSynchronouslyFromServer()テスト対象の同期ネットワーク更新ロジックを呼び出します。一時停止ポイントはこの呼び出しチェーン内にあります。 -XCTAssertNotEqual(originalSmoothies, Smoothie.all)サーバー側のデータによってローカルのスムージー リストが変更されたことを確認します。

(04:18) スピンダンプはテスト レポートに追加されます。これは、各スレッドが主にどの機能でスタックしているかを示しており、「CI がキャンセルされました」よりも実用的です。セッションでは、スピンダンプがターミナルからも実行できることも指摘しました。spindumpコマンドまたはアクティビティ モニターを使用して手動で取得します。

2. スピンダンプからデッドロックを特定する

(07:56) デモでレポートが生成された後、スピンダンプでテスト名を検索できます。testUpdatingSmoothiesFromServer。コールスタックにはテストエントリが表示されますfetchSynchronouslyFromServer()、次に入力しますperformGETRequest(to:)、そして最後にロック待ちで止まります。これは、ビジネス コードのセット全体を調べるよりも高速です。

SmoothieNetworkingTests.testUpdatingSmoothiesFromServer()
  static Smoothie.fetchSynchronouslyFromServer()
    static Smoothie.performGETRequest(to:)
      psynch_mtxcontinue

キーポイント:

  • 最初の行は、ハングを特定の XCTest メソッドにバインドします。
  • 2 行目は、テストがテスト フレームワークの外に留まらず、テスト対象の API に入ったことを示します。
  • 3 行目はヘルパー メソッドに範囲を狭めます。performGETRequest(to:)
  • psynch_mtxcontinueスレッドがミューテックス ロックを待機していることを示します。これはトランスクリプトのデッドロック分析と一致します。

(08:23) ソースコードが表示されます。fetchSynchronouslyFromServer()すでにロックされており、内部で呼び出されていますperformGETRequest(to:)同じロックを取得しようと別の試みが行われました。このヘルパーは GET リクエストのみを実行し、ロックを繰り返し取得しないでください。

extension Smoothie {

    enum Errors: Error {
        case noData
    }

    static var serverIsAvailable: Bool { false }
    static var smoothieEndpoint: URL {
        URL(string: "https://smoothies.food.com")!
    }

    static func fetchSynchronouslyFromServer() throws {
        fetchSmoothieLock.lock()
        defer { fetchSmoothieLock.unlock() }

        guard let data = performGETRequest(to: smoothieEndpoint) else {
            throw Errors.noData
        }

        let smoothies = try JSONDecoder().decode([Smoothie].self, from: data)
        Smoothie.all += smoothies
    }

    static func performGETRequest(to url: URL) -> Data? {
        defer { fetchSmoothieLock.unlock() }

        if url == smoothieEndpoint {
            fetchSmoothieLock.lock()
        }

        return performNetworkRequest(method: .get, url: url)
    }
}

キーポイント:

  • fetchSynchronouslyFromServer()まず電話してくださいfetchSmoothieLock.lock()、そして使用しますdefer終了予定時にロックが解除されます。 -performGETRequest(to:)内部的には URL が等しいsmoothieEndpointロック時間。
  • 同じ呼び出しチェーンがこのロックを繰り返し競合し、スピンダンプの待機スタックには特定の理由があります。
  • このコード スニペットは、実行時間許容値の値を示しています。保留中のテストが終了するだけでなく、ポジショニング資料がレポートに書き込まれます。

(08:43) 修正は、ヘルパー メソッドのロック ロジックを削除し、ネットワーク リクエストのみを担当するようにすることです。テストは再実行後すぐに終了し、ハングは解消されます。

extension Smoothie {

    // Omitted for brevity. See previous code snippet for content.

    static func performGETRequest(to url: URL) -> Data? {
        return performNetworkRequest(method: .get, url: url)
    }
}

キーポイント:

  • performGETRequest(to:)元の責任を保持します: GET リクエストを開始して戻りますData?
  • ロックの取得と解放がヘルパー メソッドに分散されなくなりました。
  • 上層階fetchSynchronouslyFromServer()データ更新を引き続き制御できる重要なセクション。
  • 修正後、テストのフィードバック ループは「スタックしてキャンセルされる」から「実行されて結果が得られる」に変わります。

3. デフォルトおよび最大許容時間を構成する

(05:13) デフォルトでは、各テストの許容時間は 10 分です。テストが 10 分以内に完了すると、タイマーは次のテストに向けてリセットされます。グローバルなデフォルトはテスト計画構成で調整できます。特定のテストまたはテストクラスを使用できますexecutionTimeAllowanceAPIは個別に設定します。

XCTestCase.executionTimeAllowance -> TimeInterval
values are rounded up to the nearest minute

キーポイント:

  • executionTimeAllowanceはいXCTestCaseの上TimeIntervalプロパティを使用すると、トランスクリプトはそれを特定のテストまたはテスト クラスの構成エントリとして明示的に使用します。
  • 戻り値は実行時間のより詳細な上限を表し、グローバル テスト計画のデフォルト値をオーバーライドする必要はありません。
  • 記録の説明 許容量は分単位で切り上げられます。 60秒未満の場合は60秒に切り上げ、100秒の場合は120秒に切り上げます。
  • API レベル設定は最も高い優先度を持ち、実際に時間がかかる少数のテストに適しています。

(09:18) 優先度を設定して:executionTimeAllowanceAPI が最も高く、次に xcodebuild の時間許容オプション、テスト計画設定が続き、システムのデフォルトは 10 分で最も低くなります。無制限のテストリクエストを避けるために、Xcode 12 ではテスト計画または xcodebuild で強制できる最大許容量も提供しています。

4. 並列分散テストを使用して合計時間を短縮する

(12:21) 並列分散テストは、クラスごとにさまざまな実行先にテストを分散します。各デバイスは一度に 1 つのテスト クラスを実行し、実行後に次のクラスを取得し続けます。 Xcode 12 の新機能: xcodebuild は、物理 iOS および tvOS デバイス上でテストを並行して実行できます。

parallel-testing-enabled = YES
parallelize-tests-among-destinations = YES

キーポイント:

  • parallel-testing-enabled並列テスト機能をオンにします。 -parallelize-tests-among-destinations各宛先で完全なテスト セットを実行するのではなく、指定された宛先にテストを分散するように xcodebuild に依頼します。
  • トランスクリプトには、Xcode 12 の新機能として、xcodebuild を介して物理 iOS および tvOS デバイス上でテストを並行して実行できる機能があると明示的に記載されています。 ・ 配布に参加する宛先はCI環境で提供する必要があります。同じモデルと OS バージョンのデバイス プールを最初にセッションに使用することをお勧めします。

(12:47) テストクラスを宛先に割り当てるプロセスは未定義です。機種やOSのバージョンに依存したテストの場合、配布後に再現困難な障害や音飛びが発生する可能性があります。セッションの推奨事項は、同じデバイスとシステム バージョンのデバイス プールを使用することです。プールにデバイスと OS が混在している場合は、純粋なビジネス ロジック フレームワークのテストなど、宛先に依存しないテストのみを配布します。

(14:50) 同じコードのセットがより多くの OS およびデバイスで動作することを確認することが目的の場合は、並列宛先テストを使用する必要があります。各宛先で完全なテスト スイートが実行され、個々のスイートが異なる宛先に分割されることはありません。

重要ポイント

  • **対処方法: CI の長時間実行テスト スイートの実行時間の許容を有効にします。 ** 実行する価値がある理由: テストを一時停止すると、結果バンドルに有効な結論が含まれなくなります。このセッションでは、タイムアウト後にスピンダンプを取得し、残りのテストを実行し続けるプロセスが示されています。開始方法: テスト計画でテストのタイムアウトを開き、最初はデフォルトの 10 分を使用し、次に過去の時間の経過に基づいて調整します。
  • **対処方法: 既知の遅いテストに対しては、別の許容値を設定します。 ** 実行する価値がある理由: グローバル許容値が大きすぎると、中断されたテストを検出する時間が遅くなります。小さすぎると、偶発的な損傷が発生し、より長いテストが必要になります。始め方:通信中XCTestCaseかぶせるexecutionTimeAllowance、テスト計画または xcodebuild で最大許容値を構成します。
  • **対処方法: テスト失敗のトラブルシューティング プロセスにスピンダンプを組み込みます。 ** 実行する価値がある理由: スピンダンプを使用して、テスト名からデモを見つけます。performGETRequest(to:)ロックが待っています。開始方法: CI の結果バンドルを保存します。アローアンスエラーが発生した場合は、まず添付ファイルを開いてテストメソッド名とビジネスメソッド名を検索します。
  • **対処方法: 純粋なビジネス ロジック テスト用の並列デバイス プールを作成します。 ** 実行する価値がある理由: セッション記述分散アルゴリズムは、テスト クラスに従って作業を宛先にディスパッチします。これは、デバイスの違いに依存しないテストに適しています。開始方法: CI で同じモデルと OS バージョンのデバイスを準備しますxcodebuild testコマンドに複数を追加する-destinationそして2つの平行フラグ。
  • **対処方法: 「配布テスト」と「マルチシステム検証」を 2 つの CI ジョブに分割します。 ** 実行する価値がある理由: Parallel Distributed Testing はキット時間の短縮を追求し、Parallel Destination Testing は複数の OS またはデバイスをカバーすることを追求します。開始方法: 1 つのジョブは配布に同一のデバイス プールを使用し、別のジョブはターゲット OS マトリックスで完全なテスト セットを実行します。

関連セッション

コメント

GitHub Issues · utterances