Swift Digest
ST-0001 | Swift Evolution

URLと識別子に対応する専用の.bug()関数

Dedicated .bug() functions for URLs and IDs

Proposal
ST-0001
Authors
Jonathan Grynspan
Status
Implemented (Swift 6.0)

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

01 何が問題だったのか

Swift Testing には、テストやテストスイートに付随するメタデータを表現するための「トレイト」の仕組みがあります。その中の .bug() トレイトは、テストに関連するバグ(issue、チケットなどとも呼ばれます)を紐付けるために用意されており、開発ツールとの連携が期待される機能でした。

当初の .bug() トレイトは、引数として「バグの一意な識別子」を受け取る設計になっていました。GitHub Issues や Bugzilla のようなバグトラッキングシステム上でユニークに決まる識別子を渡してもらえれば、ツール側がそれを解釈してバグの所在を提示できるだろう、という想定です。

@Test(.bug("12345"))
func example() { ... }

しかし、識別子だけを受け取る設計には早い段階で無理があると判明しました。任意のバグトラッキングシステムに対する汎用的な「識別子からバグを引く」仕組みは現実的ではなく、ツール側もその識別子をどう扱えばよいか決め手がありません。

その後の調整で、「もし渡された識別子が有効な URL であれば、それを URL として解釈してよい」という運用に変更されました。

@Test(.bug("https://github.com/.../issues/12345"))
func example() { ... }

ところが今度は、.bug() トレイトを処理するすべてのツールが「文字列が URL かどうかを自前でパースして判定する」必要が生じてしまいます。Swift Testing 自体は依存関係を最小化するため Foundation の URL などを使わない方針であり、テスティングライブラリの内側で URL の妥当性を確かめることもできません。

結果として、テストの作者は URL と不透明な識別子のどちらを渡せばよいのか分かりにくく、ツールの作者は文字列を解釈する責任を一手に引き受けることになり、設計上の負担が両者に分散していました。

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

.bug() トレイトを「URL を受け取るオーバーロード」と「不透明な識別子を受け取るオーバーロード」の2系統に分割し、テストの作者が手元の情報に応じてどちらか一方、または両方を明示的に指定できるようにします。これにより、ツール側が文字列を URL かどうか判定する必要がなくなります。

Bug

Bug 型は、URL・識別子・タイトルをそれぞれ独立したプロパティとして保持する形に整理されます。urlid はいずれもオプショナルで、利用可能な情報だけが詰められます。

public struct Bug: TestTrait, SuiteTrait, Equatable, Hashable, Codable {
    /// バグに関する追加情報を指す URL(RFC 3986 準拠の文字列)
    public var url: String?

    /// バグトラッキングシステム上の一意な識別子
    public var id: String?

    /// バグの人間が読めるタイトル
    public var title: Comment?
}

旧来の identifier プロパティ(URL を含む可能性があった)は廃止されます。

.bug() のオーバーロード

.bug() トレイトのファクトリ関数は、次の3つのオーバーロードに分割されます。

extension Trait where Self == Bug {
    // URL のみを渡す
    public static func bug(
        _ url: _const String,
        _ title: Comment? = nil
    ) -> Self

    // 数値の識別子を渡す(URL は任意)
    public static func bug(
        _ url: _const String? = nil,
        id: some Numeric,
        _ title: Comment? = nil
    ) -> Self

    // 文字列の識別子を渡す(URL は任意)
    public static func bug(
        _ url: _const String? = nil,
        id: _const String,
        _ title: Comment? = nil
    ) -> Self
}

第1引数(URL 用)にはラベルがなく、識別子側には id: ラベルが付くため、テストの作者は引数の見た目だけで「これは URL なのか識別子なのか」を判別できます。少なくとも片方にラベルを付けることで、.bug() 呼び出しの解決が曖昧にならないようにしています。

利用例は次の通りです。

// URL のみ
@Test(.bug("https://github.com/.../issues/12345", "Crash on launch"))
func example1() { ... }

// 数値の識別子のみ
@Test(.bug(id: 12345))
func example2() { ... }

// URL と文字列の識別子の両方
@Test(.bug("https://example.com/bugs/ABC-42", id: "ABC-42"))
func example3() { ... }

URL の事前検証

@Test および @Suite マクロは、URL として渡された文字列がマクロ展開時に基本的な形式チェックを行い、明らかに不正な URL であれば診断を出すように改修済みです。これにより、ツール側で URL を扱う前段で簡単な妥当性は担保されます。

ツールとの連携

ツールは Bug トレイトの urlid を、それぞれ独立した情報として読み出せます。たとえばテスト一覧やテスト結果のビューで、url があればクリック可能なリンクとして提示し、id があればバグトラッキングシステム上の識別子として表示する、といった使い分けが可能になります。

なお、テスティングライブラリの実験的なイベントストリーム出力で Bug トレイトをどう表現するかについては、別の Proposal で扱う予定とされています。