ハイライト
Swift Testing の
#expect(throws:)マクロが do-catch 定型を 1 行に、withKnownIssueが期待失敗をレポートを汚さずマーク、パラメータ化テストが 1 関数で複数入力をカバー——この 3 機能が本セッションの核心。
主要内容
テスト作成でエラー処理は最も省略されがち。XCTest で throwing 関数をテストするには do-catch 手書き——冗長。関数が throw しなければ do-catch は「テストが効いていない」ことを教えてくれない。Swift Testing の #expect(throws:) が解決:エラー型を渡せば該当型 throw で合格、しなければ失敗——1 行。
もう一つの痛点:「既知の失敗テスト」。外部依存が壊れて一時修復不能でも毎回赤表示、本当の失敗を埋もれさせる。.disabled でスキップするとコンパイルエラーも見えなくなる。withKnownIssue はより精密:テストは実行、失敗は「期待失敗」として記録し失敗総数に含めない。修復時に削除を促す。
パラメータ化テストは重複コードを解決。enum に 6 case、各 containsNuts をテスト。6 関数コピペ?ミスしやすい。for ループ?1 失敗で中断、どの値か不明。@Test(arguments:) で入力を属性に、フレームワークが各引数で独立ケース生成、失敗時に入力を明示、全ケース並列実行。
詳細
エラーアサーション:#expect(throws:)
#expect(throws:) は 4 段階の精度(02:57):
// 何か throw すればよい
#expect(throws: (any Error).self) {
try teaLeaves.brew(forMinutes: 200)
}
// 型を検証
#expect(throws: BrewingError.self) {
try teaLeaves.brew(forMinutes: 200)
}
// 特定 case に一致
#expect(throws: BrewingError.oversteeped) {
try teaLeaves.brew(forMinutes: 200)
}
// カスタム検証(関連値など)
#expect {
try teaLeaves.brew(forMinutes: 3)
} throws: { error in
guard let error = error as? BrewingError,
case let .needsMoreTime(optimalBrewTime) = error else {
return false
}
return optimalBrewTime == 4
}
キーポイント:
throws: (any Error).self— 何か throw すれば合格throws: BrewingError.self— 別型 throw で失敗throws: BrewingError.oversteeped— case 完全一致(関連値含む)- クロージャ形式 — 最柔軟。
Bool返却で判定
#require(throws:) は必須合格版(04:11):失敗時テスト即終了。
既知問題:withKnownIssue
@Test func softServeIceCreamInCone() throws {
let iceCreamBatter = IceCreamBatter(flavor: .chocolate)
try #require(iceCreamBatter != nil)
#expect(iceCreamBatter.flavor == .chocolate)
withKnownIssue {
try softServeMachine.makeSoftServe(in: .cone)
}
}
キーポイント(04:29):
withKnownIssueは失敗部分のみラップ、他アサーションは通常実行- 失敗は「期待失敗」記録、テスト失敗にカウントしない
- 根本問題修復時に削除を促す
.disabledより優位:コンパイル・実行継続
パラメータ化テスト
@Test(arguments: [IceCream.Flavor.vanilla, .chocolate, .strawberry, .mintChip])
func doesNotContainNuts(flavor: IceCream.Flavor) throws {
try #require(!flavor.containsNuts)
}
@Test(arguments: [IceCream.Flavor.rockyRoad, .pistachio])
func containNuts(flavor: IceCream.Flavor) {
#expect(flavor.containsNuts)
}
キーポイント(08:44):
@Test(arguments:)は Sendable コレクション(配列、辞書、Range 等)を受け付け- 各引数が独立テストケース、失敗時に入力を明示
- 全ケース並列——for ループより高速
- 入力が
Codableなら Xcode で失敗ケース単体再実行可
多パラメータ組み合わせ(11:07):
@Test(arguments: Ingredient.allCases, Dish.allCases)
func cook(_ ingredient: Ingredient, into dish: Dish) async throws { ... }
2 コレクションのデカルト積——4 食材 × 4 料理 = 16 ケース。全組み合わせ不要なら zip:
@Test(arguments: zip(Ingredient.allCases, Dish.allCases))
func cook(_ ingredient: Ingredient, into dish: Dish) async throws { ... }
zip は順序ペア——4 ケースのみ。
Suite、Tag、並列テスト
@Suite("Various desserts")
struct DessertTests {
@Suite struct WarmDesserts {
@Test func applePieCrustLayers() { /* ... */ }
@Test func lavaCakeBakingTime() { /* ... */ }
}
@Suite struct ColdDesserts {
@Test func cheesecakeBakingStrategy() { /* ... */ }
@Test func mangoSagoToppings() { /* ... */ }
}
}
Suite はネスト可(13:11)——論理グループ化。
Tag は Suite 横断でテスト関連付け(13:36):
extension Tag {
@Tag static var caffeinated: Self
@Tag static var chocolatey: Self
}
@Suite(.tags(.caffeinated)) struct DrinkTests { ... }
@Test(.tags(.caffeinated, .chocolatey)) func espressoBrownieTexture() { ... }
Tag は Xcode 16 テストナビゲーター/レポートに表示。テストプランで Tag 包含/除外。分布インサイトで同一 Tag 失敗の共通点を発見。
並列テストはデフォルト有効(21:20)。実行順ランダムで暗黙データ依存を暴露。依存解決前は .serialized:
@Suite("Cupcake tests", .serialized)
struct CupcakeTests { ... }
.serialized は子 Suite に継承——重複不要。
非同期コールバックテスト:confirmation
複数回発火するコールバックは手動カウントの代わりに confirmation(25:50):
@Test func bakeCookies() async throws {
let cookies = await Cookie.bake(count: 10)
try await confirmation("Ate cookies", expectedCount: 10) { ateCookie in
try await eat(cookies, with: .milk) { cookie, crumbs in
#expect(!crumbs.in(.milk))
ateCookie()
}
}
}
expectedCount で期待コールバック回数。不一致で失敗。0 で「決して発火しない」アサーション。
重要ポイント
-
do-catch テストパターンを
#expect(throws:)に置換:なぜ価値があるか:do-catch は冗長で「throw しなかった」見落としあり。始め方:throw 検証の do-catch を#expect(throws: ErrorType.self)に。 -
パラメータ化テストで重複テスト関数を撲滅:なぜ価値があるか:同一ロジック・異入力のコピペはミス多い。入力とロジック分離、新 case は配列 1 行追加。始め方:入力のみ異なるテストを
@Test(arguments:)で統合。 -
withKnownIssueで「一時修復不能」失敗を管理:なぜ価値があるか:.disabledは完全スキップでコンパイルエラーも見えない。始め方:CI で繰り返し失敗するが当面放置するテストをラップ。 -
Tag でファイル横断関連付け、テストプランで精密フィルタ:なぜ価値があるか:大規模化でディレクトリ構成だけでは不足。Tag でビジネス属性(有料機能、ネットワーク依存)横断。始め方:重要次元に Tag、テストプラン include/exclude でフィルタ。
関連セッション
- Meet Swift Testing — Swift Testing 入門:@Test、#expect、Suite 基礎
- Consume noncopyable types in Swift — Swift 6 noncopyable 型とテスト
- Explore Swift performance — Swift 性能調整、並列テストの影響
- Explore the Swift on Server ecosystem — サーバー Swift でのテストフレームワーク実践
コメント
GitHub Issues · utterances