Swift Digest
ST-0013 | Swift Evolution

test issue の severity

Test Issue Severity

Proposal
ST-0013
Authors
Suzy Ratcliff
Review Manager
Maarten Engels
Status
Implemented (Swift 6.3)

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

01 何が問題だったのか

Swift Testing でテストの実行中に問題が起きた場合、その情報は issue として記録されます。#expect の失敗や Issue.record(...) で残されるものがこれに当たり、これまでは issue を記録すると 必ずテストが失敗扱い になりました。

しかし、テストの実行中に観測した出来事の中には、「失敗とまでは言えないが結果に残しておきたい」というものもあります。

  • スナップショットテストで画像を比較する際、90% 以上の一致を合格とした上で、95% に満たない場合は「同一ではない」ことに気づけるよう知らせたい
  • ライブラリのテスト用 API に同じ引数が複数回渡されたとき、利用者の意図がある場合に備えてエラーにはせず注意だけ促したい
  • 統合テストで一次サーバが落ちていて代替サーバに切り替わった、といった 回復可能な想定外の挙動 を結果に残したい
  • テストのセットアップ中に行ったリトライが成功した場合に、リトライがあったこと自体は記録しておきたい

これらはいずれも、テストを失敗させずに結果に残すべき情報です。issue がすべて失敗を意味するという従来の前提のままでは、こうした情報を表現する手段がなく、テストの実行結果から得られる洞察が限られていました。

02 どのように解決されるのか

issue に severity(重要度) という概念を導入し、errorwarning の 2 段階で表せるようにします。error の issue は従来通りテストを失敗させますが、warning の issue はテストを失敗させず、結果に「警告として記録された」情報として残ります。

Issue.Severity

severity は Issue のネスト型 Issue.Severity として定義されます。Comparable に適合しており、warning < error の順序を持ちます。

extension Issue {
  public enum Severity: Codable, Comparable, CustomStringConvertible, Sendable {
    case warning
    case error
  }
}

warning として issue を記録する

Issue.record(...)severity: パラメータが追加され、.warning を渡すことで失敗扱いにならない issue を記録できます。デフォルトは .error で、これまでの呼び出しと同じ挙動になります。

Issue.record("My comment", severity: .warning)

メソッドのシグネチャは次の通りです。

extension Issue {
  @discardableResult
  public static func record(
    _ comment: Comment? = nil,
    severity: Severity = .error,
    sourceLocation: SourceLocation = #_sourceLocation
  ) -> Self
}

なお、既存の severity: を持たない Issue.record(_:sourceLocation:) も、関数値として参照しているコードを壊さないために残されますが、deprecated 扱いとなり新しい API の利用が推奨されます。

IssueseverityisFailure

Issue には新しく 2 つのプロパティが加わります。

  • severity: その issue の severity を取得・設定できるプロパティ
  • isFailure: その issue がテストの失敗とみなされるかを表す Bool の computed property
extension Issue {
  public var severity: Severity { get set }

  public var isFailure: Bool { get }
}

isFailure は、severity が .error 以上で、かつ withKnownIssue(...) で「既知の issue」として扱われていない場合に true になります。「失敗かどうか」を判定したいときは、severity を直接比較するのではなく isFailure を使うことが推奨されます。これは将来 severity の段階が増えても、isFailure を見ているコードはそのまま動き続けるようにするためです。

isFailureseverity は、たとえば withKnownIssue のマッチング条件で次のように組み合わせて使えます。

withKnownIssue {
  // ...
} matching: { issue in
  issue.isFailure || issue.severity > .warning
}

コンソール出力

.warning の issue が記録されたテストは、失敗せずに合格として扱われ、警告が記録された旨が出力に含まれます。

◇ Test "All elements of two ranges are equal" started.
⚠ Test "All elements of two ranges are equal" recorded a warning at ZipTests.swift:32:17: Issue recorded
↳ My comment
✔ Test "All elements of two ranges are equal" passed after 0.001 seconds with 1 warning.

イベントストリームとの連携

JSON 形式のイベントストリームにも severity と isFailure が反映されます。issueRecorded イベントの issue オブジェクトに severityisFailure の 2 つのフィールドが追加されます。

 <issue> ::= {
   "isKnown": <bool>,
+  "severity": <string>,
+  "isFailure": <bool>,
   ["sourceLocation": <source-location>,]
 }

これは「すべての issueRecorded は失敗を意味する」と仮定していたツールにとっては破壊的変更にあたるため、イベントストリームのバージョンが上がります。従来通りの v1 では挙動を維持し、.warning の issue については issueRecorded イベントを出力しません。新しいバージョンを利用するツールは、isFailure を見て失敗かどうかを判定することになります。

03 今後の見通し

将来の発展として、以下のようなアイデアが挙げられています。

  • 警告として記録された issue を、より厳しいテスト構成のもとでエラーに 昇格 させ、警告も失敗扱いとして実行できるようにする
  • infodebug といった、warning よりさらに弱いレベルの severity を追加し、ユーザーが結果に残したい情報をより細かく分類できるようにする

これらはいずれも将来の構想であり、実現を約束するものではありません。