Transferable な添付物
Transferable Attachments
このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら↗。
01 何が問題だったのか
ST-0009 で導入された Attachment 型は、テストに任意のデータを添付するための仕組みです。Attachment には、添付対象が Attachable かつ Encodable、または Attachable かつ NSSecureCoding に適合する場合のために、バイト列への変換を肩代わりするデフォルト実装が用意されていました。
一方、Apple プラットフォームには CoreTransferable フレームワークがあり、その中の Transferable プロトコルは値とバイナリデータの相互変換を行うための標準的な仕組みとして広く使われています。SwiftUI や AppIntents をはじめ、多くの公開 API がこのプロトコルを前提に組み立てられており、すでに Transferable に適合した型を持っているケースは少なくありません。
しかし、これまでは Transferable に適合しているだけではテストに添付することができず、テスト作者は別途 Attachable 用のラッパーを書くなど、本来のテストロジックとは関係のない準備コードを毎回書く必要がありました。Encodable や NSSecureCoding と同じように、Transferable についてもデフォルトの添付経路を用意しておくのが自然です。
02 どのように解決されるのか
Transferable に適合した値をそのまま Attachment に渡せるようにするための新しいイニシャライザが追加されます。内部的には、Transferable 値をラップする AttachableWrapper の実装(_AttachableTransferableWrapper)が用意され、添付時に Transferable の exported(as:) を呼んでバイト列に変換します。
使い方
Attachment(exporting:as:named:sourceLocation:) に Transferable 値と添付したいコンテントタイプを渡すだけで、テストに添付できます。exported(as:) は async throws なので、このイニシャライザも async throws です。
import Testing
import CoreTransferable
@Test func menuNotEmpty() throws {
let menu = FoodTruck.menu
if menu.isEmpty {
let attachment = try await Attachment(exporting: menu, as: .pdf)
Attachment.record(attachment)
Issue.record("The food truck's menu was empty")
}
}
struct Menu: Transferable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(exportedContentType: .pdf) { menu in try await menu.pdfData() }
}
}
ここでは Menu が Transferable に適合しており、PDF として書き出すための DataRepresentation を持っています。テスト側ではラッパー型を別途用意する必要はなく、exporting: 経由でそのまま添付できます。
新しいイニシャライザ
Attachment に追加されるイニシャライザは次のシグネチャです。
@available(macOS 15.2, iOS 18.2, tvOS 18.2, visionOS 2.2, watchOS 11.2, *)
extension Attachment {
public init<T>(
exporting transferableValue: T,
as contentType: UTType? = nil,
named preferredName: String? = nil,
sourceLocation: SourceLocation = #_sourceLocation
) async throws where T: Transferable, AttachableValue == _AttachableTransferableWrapper<T>
}
各引数の役割は次のとおりです。
transferableValue: 添付したいTransferable値。contentType: 書き出しに使うUTType。nilの場合、テスティングライブラリは値のexportedContentTypes(_:)を呼び、そこから返ってきた中でUTType.dataに適合する最初のものを選びます。preferredName: 添付ファイルの希望ファイル名。nilの場合は値から妥当な名前が生成されます。sourceLocation: 添付に関する issue を記録する際に使われる位置情報。
戻り値の型は Attachment<_AttachableTransferableWrapper<T>> になります。_AttachableTransferableWrapper は AttachableWrapper に適合する内部型で、Transferable 値の保持と書き出しを引き受けます。
非同期・throws である理由
Transferable の exported(as:) は時間のかかる処理になりうるため async、また失敗しうるため throws です。たとえば指定された UTType に対応する表現がない場合や、ディスク上のファイルに裏付けられた値を読み出す途中で I/O エラーが発生した場合などに失敗します。これらの失敗は、添付の組み立て時点でローカルに扱える形で throw され、Attachment.record(_:sourceLocation:) 自体は従来どおり同期で throws を伴わないシンプルな API のままに保たれます。