Swift Testing

Swift Testingは公式から出たSwift用テストフレームワークです。
`XCTest`と混在できるため、今日からSwift Testingでテストを書くことができます。

# 簡単なXCTestからSwift Testingへの書き換え

```テスト対象.swift
extension Date {
  var christmas: String {
    "12/25"
  }
}
```

```XCTest.swift
import XCTest

final class DateTests: XCTestCase {
  func testChristmas() {
    XCTAssertEqual(Date.christmas, "12/25")
  }
}
```

```Swift Testing.swift
import Testing

struct DateTests {
  @Test("christmas extensionのテスト")
  func christmas() {
    #expect(Date.christmas == "12/25")
  }
}
```

# 構成要素
### `＠Test`
テストを宣言するマクロ。関数の前に追加する。
XCTestでは`XCTestCase`を継承し、関数名の頭に`test`をつける。

### `@Suite`
複数のテストをまとめるマクロ。
テスト関数を含むクラスが自動的にテストスイート（複数のテストをまとめたもの）になる。
```swift
struct DateTests { // 自動的にテストスイートになる。
  @Test func christmas() {
    //...
  }
}
```
表示名やtraitを付与したい場合は@Suiteマクロを使用する。
```swift
@Suite(.timeLimit(.minutes(60))
struct DateTests {
  @Test func christmas() {
    //...
  }
}
```

### `期待値の確認`
`#expect(_:_:sourceLocation:)`または`#require(_:_:sourceLocation:)`を使用し評価します。
```XCTest.swift
final class EventTests: XCTestCase {
  func finalYearlyEvents() throws {
    XCTAssertNotNil(events.last)
    XCTAssertEqual(events.last, .christmas)
  }
}
```
```Swift Testing.swift
struct EventTests {
  @Test func finalYearlyEvents() throws {
    try #require(events.last != nil)
    #expect(events.last == .christmas)
  }
}
```

### `Traits`
テストやテストスイートに特性を付与することで、テストに注釈を付けたり動作をカスタムすることができる。
それぞれの使い方は公式ドキュメントに詳しく書かれているので、概要とリンクのみとします。

- [enabled/disabled](https://swiftpackageindex.com/swiftlang/swift-testing/main/documentation/testing/enablinganddisabling)
テストを条件付きで有効または無効にする。
- [timeLimit](https://swiftpackageindex.com/swiftlang/swift-testing/main/documentation/testing/limitingexecutiontime)
テストの制限時間を設定する。
複数の制限時間が設定されている場合、一番短いものが適応される。
テストスイートに制限時間を宣言すると、そのスイート内全てのテストスイートと関数に適応される。
- [serialized](https://swiftpackageindex.com/swiftlang/swift-testing/main/documentation/testing/parallelization)
並列実行/順次実行を制御します。
- [bug](https://swiftpackageindex.com/swiftlang/swift-testing/main/documentation/testing/associatingbugs)
バグとテストを関連つけます。
- [tags](https://swiftpackageindex.com/swiftlang/swift-testing/main/documentation/testing/addingtags)
テストやテストスイートにタグをつけることができる。
- [コメント（//,/**/）](https://swiftpackageindex.com/swiftlang/swift-testing/main/documentation/testing/addingcomments)
コードにコメントを追加しテスト結果に情報を提供する。

# 新しい機能
## パラメイトライズ
Swift Testing特有の機能です。異なるパラメータを外部から注入することで、1つのテスト関数から複数のテストを生成することができます。

```swift
enum Event {
  case valentine, halloween, christmas

  var day: String { ... }
}
```
XCTestでこのようなテストがあるとします。
```XCTest.swift
final class EventTests: XCTestCase {
  func testDay() {
    XCTAssertEqual(Event.valentine, "2/14")
    XCTAssertEqual(Event.halloween, "10/31")
    XCTAssertEqual(Event.christmas, "12/25")
  }
}
```
この場合仮にテストが失敗したら再実行は`testDay`単位でしかできず、3つの判定が再度走ることとなります。
一方でこのテストをSwift Testingのパラメイトライズを活用するとこのように書き換えられます。
```Swift Testing.swift
struct EventTests {
  @Test(arguments: [
    (Event.valentine, "2/14"),
    (Event.halloween, "10/31"),
    (Event.christmas, "12/25")
  ])
  func testDay(event: Event, expected: String) {
    #expect(event.day == expected)
  }
}
```
テストの実行は`testDay`が各パラメータで3回実行される形になります。どのパラメータで失敗したか一目でわかり、失敗したパラメータのみ再実行できます。

引数は`arguments`で指定した順にテスト関数で参照できます。

## 並列実行と順次実行
テストスイートやテスト関数は指定しない場合、自動で並列実行されます。
順次実行したい場合は`serialized`トレイトを付与します。

※ テスト関数に付与する場合は、テスト関数がパラメイトライズを使用していなければ効果はありません。

```Swift Testing.swift
struct EventTests {
  @Test(.serialized, arguments: [
    (Event.valentine, "2/14"),
    (Event.halloween, "10/31"),
    (Event.christmas, "12/25")
  ])
  func testDay(event: Event, expected: String) {
    #expect(event.day == expected)
  }
}
```
これにより、3ケース並列してテストされず、前のテストを待ってから次のテストが開始されます。

## エラーのテスト
`withKnownIssue`を使用すると、既知の問題のみが発生した場合テストは失敗ではなく成功と判定されます。
※既知のエラーに関しては`#expect(throws:)`ではなく`withKnownIssue`を使用することが推奨されます。


```swift
struct EventTests {
  @Test func finalYearlyEvents() throws {
    try #require(events.last != nil)
    #expect(events.last == .christmas) // これが既知のエラーの場合
  }
}

struct EventTests {
  @Test func finalYearlyEvents() throws {
    try #require(events.last != nil)
    withKnownIssue("最後のイベントは年越しです。") {
      #expect(events.last == .christmas)
    }
  }
}
```

# 参考
https://swiftpackageindex.com/swiftlang/swift-testing/main/documentation/testing