XCTIssueを使用してテストの失敗をトリアージする – WWDC2020

Session概要

Xcodeでの最新のTesting APIを用いてキャッチされていない問題をトリアージし、分析する方法について。
テストワークフローを簡単にする方法、テストでの失敗をコンテキストに組み込む方法について。
Xcode12で追加されたテスト失敗時のレポートUIを拡張した内容について https://developer.apple.com/videos/play/wwdc2020/10687/

Xcode12でのXCTest失敗時のUI

  • 失敗した前のコードにて注釈コメントを表示
  • Issue Navigator でテストコードのコールスタックが確認可能
    • コールスタックの行をクリックするとコードにジャンプ



上記はTestレポート画面でコールスタックからコードへジャンプしたり、アシスタントボタンから対象コードを2分割で表示することが可能

Swift Errors in Tests


Swiftランタイムの改良により、iOS13.4, macOS10.15.4からthrowされたエラーと対象のソースコードの位置がわかるようになったため、エラーのコンテキストを把握することが容易になった。



Xcode12より、setupWithError, tearDownWithErrorが追加された

リッチな失敗オブジェクト


XCTestは失敗を4つの値に分類する
  1. 失敗メッセージ
  2. ファイルパス
  3. 失敗が記録された行番号
  4. 失敗の予想を示すフラグ

予想された失敗はXCTAssertに表示される。
予期せぬ失敗はthrowされた例外をXCTestが捉えること。
失敗したデータはXCTAssertから recordFailure APIに渡され、失敗を記録してXcodeに伝えられていた。
Xcode12では、XCTIssueで↑の4つを保持し追加で、明示型タイプの計算方詳細な記述, 内在するエラー, 添付がある。

XCTAttachment


XCTAttachmentは任意のデータをキャプチャするAPIで、テスト自体もしくは、XCTContextによるアクティビティに添付する。
XCTIssueにも紐付け可能。

XCTestCaseの新しいAPI


失敗を記録するためのAPI
直接呼び出しやオーバーライド可能

XCTIssue

コールスタックをキャプチャしシンボル化出来る。
そのため、複雑なテストコードの失敗に対するコンテキストが増える。

例えば、下記のテストコードでは失敗した箇所と、どこのコードで失敗したかを明確に把握することが出来る

XCTIssueを用いたカスタムTestアサーション

func assertSomething(about data: Data,
                         file: StaticString = #filePath,
                         line: UInt = #line) {

        // Call out to custom validation function.
        if !isValid(data) {

            // Create issue, declare with var for mutability.
            var issue = XCTIssue(type: .assertionFailure, compactDescription: "Invalid data")

            // Attach the invalid data.
            issue.add(XCTAttachment(data: data))

            // Capture the call site location as the point of failure.
            let location = XCTSourceCodeLocation(filePath: file, lineNumber: line)
            issue.sourceCodeContext = XCTSourceCodeContext(location: location)

            // Record the issue.
            self.record(issue)
        }
    }
record APIをoverrideし、Xcodeへのログ記録をカスタム
override func record(_ issue: XCTIssue) {
    // 特定の条件で記録する
    if shouldLog(issue) {
        print("I just observed an issue!")
    }

    // If you don't want to record it, just return.
    // 特定の条件で何も記録しない
    if shouldSuppress(issue) {
        return
    }

    // Otherwise pass it to super.
    super.record(issue)
}
XCTIssueにXCTAttachment を付与することで失敗の診断および分析に役立つ
override func record(_ issue: XCTIssue) {
    
    // Redeclare using var to enable mutation.
    var issue = issue

    // Add a simple attachment.
    issue.add(XCTAttachment(string: "hello"))
    
    // Pass it to super.
    super.record(issue)
}
最新情報をチェックしよう!