🔎

Swiftの新テストライブラリ「swift-testing」特徴と導入

2023/10/16に公開1

公開日: 2023年10月
本記事は、2023年10月時点の情報に基づいています。swift-testingの進化や変更については、公式のドキュメントや関連リソースを参照してください。

はじめに

Swiftの開発環境は日々進化を続けています。その中でもユニットテストの領域において、Appleが最近公開したswift-testingライブラリが注目を浴びています。このライブラリに組み込まれているTestingというフレームワークは、多彩な新機能を持ち合わせており、現在のXCTestから大きく進化しています。現段階ではXcodeとの直接的な統合は見られませんが、将来的には統合される可能性があります。
今回の記事では、この新ライブラリの特徴、XCTestとの違い、そして移行方法について詳しく解説していきます。

背景: なぜswift-testingなのか?

XCTestの歴史

XCTestは、もともとObjective-Cのテストライブラリとして開発され、後にSwiftにも対応しました。しかし、Swiftとの一部の相性問題や、現代のベストプラクティスとの乖離が指摘されてきました。例として以下が挙げられます。

  • テストを発見するためにObjective-Cランタイムに依存していること
  • Swiftで利用できないNSInvocationのようなAPIに依存していること
  • テストサブクラスでの暗黙的アンラップ型(IUO)プロパティが必要になること
  • Swift Concurrencyとシームレスに統合するのが困難であること

新しい方向性

上記の問題に対応するために、swift-testingという新しいライブラリの提案とリリースがなされました。これはXCTestの新たな代替となり数年の間に移行される可能性があります。

このライブラリに組み込まれているTestingというフレームワークには以下の3つの重要なコンポーネントが含まれています

  1. #expect()および#require()
  2. @Testおよび@Suite
    • テスト関数とテストスイートの型を宣言するためのAttached Macros
  3. Traits
    • テストのカスタマイズを支援する機能

Swiftの新しいアプローチは、現代的な設計を取り入れ、Objective-Cランタイムの依存を少なくしています。これにより、Swift Concurrencyともスムーズに組み合わせることができます。さらに、本ライブラリを使用すると、テストの検出や実行を効率的に管理できるだけでなく、パラメータを持ったテストやその他の高度なテストケースもサポートできます。この新しいAPIは、Swiftでのテストの作成や実行をさらに向上させるための強力な基盤となります。

ここからは、上記で述べた重要な3つのコンポーネントについて、詳しく説明します。

#expect()#require()について

XCTAssertには、XCTAssertEqual()XCTAssertLessThan()など、40以上のテストメソッドがあります。このような特定な複数の期待値を評価するAPIは、JUnitRSpecといったテストライブラリでも見られます。しかし、これらのAPIには以下のような拡張性の問題点があります。

  • APIの数が多いため、新規ユーザーの学習曲線が急になり、誤解や不明確なテスト結果のリスクが高まること
  • 一部の複雑な使用ケースがサポートされていない可能性があること。
  • テストライブラリのメンテナが、複数のユースケースをサポートするために専用APIを追加する必要があり、メンテナンスコストが高くなること
  • 関数のオーバーロードが増えることで、適切な関数の選択や理解が難しくなり、コードの複雑さが増してしまうこと

新しい#expect()および#require()というExpression Macrosを導入することで、40以上存在していたテストメソッドの数を極端に減らすことができます。また、このExpression Macrosの導入によりテスト期待値の情報量を増やすことができます。例えば、以下の期待値は、失敗時にfalseのみならず、関連するオペランドや演算子の情報もキャプチャします。

以下の例を見てみましょう。以下の例では、#expect(a.contains(b))の失敗時の出力情報がXCTAssertTrueの場合と比較し情報量が増えていることが確認できます。

let array = [1, 2, 3]
let int = 4
XCTAssertTrue(array.contains(int)) // XCTAssertTrue failed
#expect(array.contains(int))       // failed: (array → [1, 2, 3]).contains(int → 4)

XCTestで利用されているテストメソッドとswift-testingで利用されているテストメソッドの比較は以下の表のようになります。この表はMigrating a test from XCTestから引用しています。

XCTest swift-testing
XCTAssert(x), XCTAssertTrue(x) #expect(x)
XCTAssertFalse(x) #expect(!x)
XCTAssertNil(x) #expect(x == nil)
XCTAssertNotNil(x) #expect(x != nil)
XCTAssertEqual(x, y) #expect(x == y)
XCTAssertNotEqual(x, y) #expect(x != y)
XCTAssertIdentical(x, y) #expect(x === y)
XCTAssertNotIdentical(x, y) #expect(x !== y)
XCTAssertGreaterThan(x, y) #expect(x > y)
XCTAssertGreaterThanOrEqual(x, y) #expect(x >= y)
XCTAssertLessThanOrEqual(x, y) #expect(x <= y)
XCTAssertLessThan(x, y) #expect(x < y)
XCTAssertThrowsError(try f()) #expect(throws: any Error.self) { try f() }
XCTAssertNoThrow(try f()) #expect(throws: Never.self) { try f() }
try XCTUnwrap(x) try #require(x)
XCTFail("…") Issue.record("…")

上記の表から分かるように多くのテストメソッドが#expect()に置き換えが可能であることがわかります。

エラーの検証およびオプショナルの処理について

テストにおいては、特定の状況下でエラーが発生するかどうか、あるいは発生しないかどうかを確認する必要がある場合があります。さらに、テストを進行する上で成功が保証されているべき期待値が存在するケースも考えられます。
このようなシチュエーションを扱うために、#require()というMacroを利用できます。具体的には、このMacroにオプショナル値を渡すことで、値が存在する場合はその値を取得し、nilの場合にはテストを終了させることができます。これまでの方法としてはXCTUnwrap()を使っていましたが、このMacroはその代わりとして考えることができます。

let someInt: Int? = 10
let someString: String? = nil
let int = try #require(someInt) // ✅
let string = try #require(someString) // ❌ Expectation failed: someString

加えて、swift-testingwithKnownIssue()という関数が用意されています。この関数は、既知の問題点を明示的に示す目的で使用されます。これにより、既知のバグを持つテスト、時々失敗するテスト、未完了のコードに関するテストなどを実行しても、それらのテストが失敗としてマークされないようにすることができます。この機能を有効にするためには、isIntermittent引数をtrueに設定する必要があります。(isIntermittentfalseとして実行するとテストでエラーが投げられると、テスト失敗としてマークされます)

@Test func error() throws {
  withKnownIssue("なぜかランダムにErrorが発生する😢", isIntermittent: true) {
    try throwErrorRandom() // ✅ エラーが投げられても、テスト失敗としてはマークされない
    #expect(100 == 100)
  }
}

@Testについて

swift-testingを導入することで、テスト関数をグローバル関数または型内のインスタンスまたは静的メソッドとして定義することが可能となります。ただしテスト関数に対して、常に明示的に@Testを付与する必要があります。

これまではXCTestCaseを継承したクラス内にテストメソッドを定義する必要があり、かつ、テスト関数名はtestで始める必要がありましたがこの制約がなくなります。
さらにテスト関数にはasync, throws, またはmutatingのキーワードを含めることができます。

以下のように@Test#expectを組み合わせることで柔軟なテストを記述することが可能になります。

import Testing

@Test func 挨拶の文字列が正しいこと() { // ✅ グローバル関数として定義可能
  let greeting = "Hello Swift"
  #expect(greeting == "Hello Swift")
}

final class SomeTests {  
  @Test func someValue999であること() async throws { // ✅ asyncとthrowsのキーワードを含めることが可能
    let someValue = try await makeSomeValue()
    #expect(someValue == 999)
  }
  
  @Test static func someStaticTests() { /* ... */ } // ✅ 静的メソッドとして定義可能
}

テスト名のカスタマイズについて

@Test属性に文字列リテラルを引数として指定することで、IDEやコマンドラインに表示されるテスト関数の名前をカスタマイズできます。

たとえば、以下の2つのテストがあるとします。

@Test("〇〇の保存処理に関するテスト") func someSaveTest() { /* ... */ }
@Test func anotherSaveTest() { /* ... */ }

この場合、Xcodeでの出力は次のようになります。

◇ Test "〇〇の保存処理に関するテスト" started.
✔ Test "〇〇の保存処理に関するテスト" passed after 0.001 seconds.
◇ Test anotherSaveTest() started.
✔ Test anotherSaveTest() passed after 0.001 seconds.

@Test属性の実装は以下のようになっており、displayNameの引数に文字列を指定することができます。

@attached(member) @attached(peer) public macro Suite(
  _ displayName: _const String? = nil,
  _ traits: any SuiteTrait...
) = #externalMacro(module: "TestingMacros", type: "SuiteDeclarationMacro")

テスト関数の外観や動作をさらにカスタマイズするには、tags(_:)のようなTraitsを使用する必要があります。これについては、Traitsの節で詳しく説明します。

アクターの分離について

XCTestでは、デフォルトで同期的なテストメソッドをメインアクターで実行していました。一方、swift-testingでは、すべてのテスト関数を任意のタスクで実行します。そのため、テスト関数をメインスレッドで実行したい場合、@MainActorを使用してメインアクターに隔離するか、MainActor.run(resultType:body:)を利用してコードを実行する必要があります。

final class MainThreadTests {
  // ❌ メインスレッドで実行されないのでテストは失敗する
  @Test() func isMainThread1() {
    #expect(Thread.isMainThread)
  }
    
  // ✅ @MainActorを利用しているのでテストは成功する
  @Test @MainActor func isMainThread2() {
    #expect(Thread.isMainThread)
  }
  
  // ✅ @MainActorを利用しているのでテストは成功する
  @Test func isMainThread3() {
    Task { @MainActor
      #expect(Thread.isMainThread)
    }
  }
  
  // ✅ @MainActorを利用しているのでテストは成功する
  @Test func isMainThread4() async {
    await MainActor.run { #expect(Thread.isMainThread) }
  }

パラメータ化されたテストについて

パラメータ化されたテストは、異なる入力セットでの同一テストの反復実行をサポートします。これは特定の入力セット、例えば列挙のすべてのケース、整数の範囲、または複数のコレクションの組み合わせなど、多くの異なるシナリオで同じテストを実行する際に非常に便利です。

テスト関数が配列や列挙のすべてのケースをループして検証すると、どの入力値で失敗したかを特定するのが難しくなる場合があります。

enum MonsterType: CaseIterable {
  case fire
  case water
  
  func canSomething() -> Bool { /* ... */ } 
  func isSomething(int: Int) -> Bool { /* ... */ } 
}

@Test func monsterSomeTest() {
    for type: MonsterType in [.fire, .water] { 
        #expect(type.canSomething())
    }
}

この問題を解決するために、本ライブラリには@Test属性を通じてパラメータをテスト関数に渡す機能を提供しています。この方法でテストが失敗すると、どの入力値で問題が発生したかを簡単に特定することができます。

例えば、enum MonsterTypeに新しいケースが追加された場合、その新しいケースも自動的にテスト関数で検証されます。また、整数の範囲をテスト関数に渡して、その範囲内のすべての整数でテストを実行することもできます。ただし、非常に大きな範囲を使用すると、テストの実行に長い時間がかかるか、完了しない可能性があるため注意が必要です。

enum MonsterType: CaseIterable { /* ... */ }

@Test(arguments: [MonsterType.fire, .water])  // ✅ 特定のケースを指定しテスト関数に渡すことができる
func someMonsterTest1(_ type: MonsterType) {
  #expect(type.canSomething())
}

@Test(arguments: MonsterType.allCases) // ✅ `CaseIterable`に準拠している場合は`allCases`を利用できる
func someMonsterTest2(_ type: MonsterType) {
  #expect(type.canSomething())
}

@Test(arguments: 1...99)  // ✅ 整数の範囲をテスト関数に渡すことができる
func someIntTest(_ int: Int) {
  let fireType: MonsterType = .fire
  #expect(fireType.isSomething(int: int))
}

複数のコレクションを同時に使用する場合、デカルト積という考え方に基づいて、すべての可能な組み合わせでテストが実行されます。しかし、zip()関数を使用すると、2つのシーケンスの要素をペアリングして、一度だけテストを実行することができます。

enum MonsterType: CaseIterable { /* ... */ }

@Test(arguments: MonsterType.allCases, 1...99) // ✅ テストは(2 × 99 = 198)回呼ばれる
func someMonsterTest3(_ type: MonsterType, int: Int) {
  #expect(type.isSomething(int: int))
}


@Test(arguments: zip(MonsterType.allCases, 1...99)) // ✅ テストの実行は`(.fire, 1)`と`(.water, 2)`の2回になる
func someMonsterTest4(of _ type: MonsterType, int: Int) {
  #expect(type.isSomething(int: int))
}

将来Variadic Genericsのサポートが拡張されることで、パラメータ化されたテストのパフォーマンスはより向上します。

テストの利用制限について

テスト関数を新しいバージョンのOSや特定のSwiftバージョンでのみ実行したい場合、@available属性をテスト関数の宣言時に使用することで、そのテストの実行を制限することができます。
さらに、@available属性のmessage引数を使うことで、テストが実行できない状況時のログメッセージを指定することも可能です。

@available(macOS 11.0, *)
@available(swift, introduced: 5.9, message: "Swift 5.9 が必要")
@Test func someTest() { /* ... */ }

@Suiteについて

複数のテスト関数を管理する際、それらをテストスイートにまとめると便利です。struct, actor, classには@Suite属性を適用できます。この@Suite属性を適用することで、それらをテストスイートとして認識します。
struct, actor, class内に@Test関数が含まれている場合、それらは自動的にテストスイートとして扱われるため、明示的に@Suite属性を付ける必要はありません。ただし、Traits(下記参照)を指定する際は、@Suite属性を明示的に記述する必要があります。

struct SomeStructTest { // ✅ @Testが含まれるので暗黙的にテストスイートとして扱われる。
  private let someBoolValue: Bool
  
  init() {
    someBoolValue = true
  }
    
  @Test func boolの値がtrueであること() {
    #expect(someBoolValue == true)
  }
}

@Suite struct SomeStructTest {  // ✅ 明示的に記述することも可能
  @Test func someTest() { /* ... */ }
}

テストクラスの変換について

XCTestでは、XCTestCaseを継承するために主にclassを使用していました。しかし、swift-testingでは、structactorを用いてテストをまとめることが可能です。
Swiftのコンパイラは並行性の安全性を強化するために、クラスの代わりに構造体やアクターの使用が推奨されます。

struct MyStructTest {  // ✅ structを利用することが可能
  @Test someTest() { /* ... */ }
}

actor MyActorTest {  // ✅ actorを利用することが可能
  @Test someTest() { /* ... */ }
}

final class MyClassTest {  // ✅ classを利用することが可能
  @Test someTest() { /* ... */ }
}

XCTestsetUpを使用していた部分は、initに変換することができます。
また、tearDownを使用していた部分はdeinitに変換することができます。ただし、構造体ではdeinitは利用できないため、deinitが必要な場合はactorまたはclassを使用する必要があります。さらに、classを使用する場合、そのクラスはfinalで宣言する必要があります。

struct MyStructTest {
  private let someStringValue: String
  
  init() {  // ✅ setUpの代替としてinitを利用する
    someStringValue = "Hello World"
  }
  
  /* ... */
}

final class MyClassTests {  // ✅ classを利用する場合はfinalが必須となる
  private let someBoolValue: Bool?
  
  init() { /* ... */ }
  
  deinit {  // ✅ tearDownの代替としてdeinitを利用する
    someBoolValue = nil  
  }
    
  /* ... */
}

テストのサブグループ化について

あるSuite属性に準拠している型に対して、ネストとしてさらにSuite属性に準拠している型を定義することができます。これによりテストをサブグループをとして形成することが可能となります。

struct UserTests {  // ✅ 暗黙的にテストスイートとして扱われる
  @Test func someUserTest() { /* ... */ }

  @Suite(.tags("友達のテスト"))
  struct FriendsTests {  // ✅ テストスイートをネストさせることが可能
    @Test func myFriendsTest() { /* ... */ }
    @Test func yourFriendsTest() { /* ... */ }
  }
}

これを利用する事により、ネストされたSuiteな型にテストの特徴を指定し、ネスト内に存在するすべてのテストに対してその特徴を継承をさせることができます。上記の例では、FriendsTests.tags("友達のテスト")TraitはmyFriendsTestおよびyourFriendsTestの両方に対してタグ"友達のテスト"を追加する効果があります。もちろんFriendsTestsにも追加されます

イニシャライザーの必要性について

テスト関数がインスタンスメソッドとして宣言されている場合、そのテスト関数で使用されるすべてのインスタンスは引数なしのイニシャライザか、デフォルト引数を持つイニシャライザで初期化する必要があります(これが暗黙的なものであるか、明示的なものであるか、同期であるか非同期であるかに関わらないです)。
この条件を満たさない場合、コンパイルエラーが生じます。ただし、テスト関数が静的メソッドとして宣言されている場合、イニシャライザの有無に関わらずコンパイルエラーは発生しません。

struct SomeStructTest { // ✅ 暗黙的なinit()が存在している
  private let someBoolValue = true
  /* ... */
}

struct SomeStructTest { // ✅ 明示的なinit()が存在している
  private let someBoolValue: Bool
  
  init() {
    someBoolValue = true
  }
  
  /* ... */
}

struct SomeStructTest { // ✅ デフォルト引数を持つinit()が存在している
  private let someBoolValue: Bool
  
  init(someBoolValue: Bool = true) {
    self.someBoolValue = someBoolValue
  }
  
  /* ... */
}

struct SomeStructTest {  
  private let someBoolValue: Bool
  
  @Test func someBoolValuetrueであること() { /* ... */ } // ❌ ERROR: init()が存在しない
  
  @Test static func someBoolValuefalseであること() { /* ... */ } // ✅ staticメソッドなのでinitがなくてもコンパイルエラーは発生しない
  
}

テストスイートの型の利用制限について

@available属性は@Test属性が付与されたテスト関数の実行時の利用可能性を制限するために使用できます。しかし、テストスイートの型には@available属性を適用することはできません。
この条件を満たさない場合、コンパイルエラーが発生します。

@available(iOS 16.0, *) // ❌ The @Suite attribute cannot be applied to this structure because it has been marked @available(iOS 16.0, *).
@Suite struct SomeTests { /* ... */}

Traitsについて

テストのTraitsを指定する仕組みを持つことは重要です。swift-testingでは、この仕組みを実現するために、@Test@SuiteAttached Macrosとして実装されています。このAttached Macrosの特徴として、Macroの宣言時に引数としてパラメータを設定できる点が挙げられます。これにより、Traitsを直接引数として渡すことが可能となります。具体的には、TraitTestTraitSuiteTraitという3つのプロトコルが利用できるようになっています。

// 基底となるプロトコル
public protocol Trait: Sendable { /* ... */ }

// @Testの引数として追加可能なTraitを表すプロトコル
public protocol TestTrait: Trait { /* ... */ }

// @Suiteの引数として追加可能なTraitを表すプロトコル
public protocol SuiteTrait: Trait { /* ... */ }

@Test@Suiteの宣言は以下のようになっており、引数としてそれぞれany TestTrait...any SuiteTrait...を渡すことが可能であることがわかります。

@attached(peer)
public macro Test(
  _ displayName: _const String,
  _ traits: any TestTrait...   // 0個以上の`TestTrait`を引数に取れる
)

@attached(member) @attached(peer)
public macro Suite(
  _ traits: any SuiteTrait...  // 0個以上の`SuiteTrait`を引数に取れる
)

ここからはTestTraitSuiteTraitとして実装されている複数のTraitの説明をします。

.comment Traitについて

テストの失敗や問題点は、CIのインターフェースやログファイルなど、テストのソースコードを直接参照できない場所で表示されることが多いです。したがって、これらの場所でテストに関連するコメントを見ることは、問題の解決を迅速に進めるのに役立ちます。

.comment()@Test属性のTrait引数として明示的に指定することで、コメントを追加することができます。

@Test(
  .comment("これがsomeTestのコメント。テストが失敗するとXcode上やCI上に表示される")
)
func someTest() {
  let myValue = 100
  #expect(myValue.value == 100)
}

上の例では、コメントをハードコーディングしていますが、enumなどを使用してコメントを定義し、複数のテストからその定義を参照することで、繰り返しの入力を減少させることが可能です。

.comment()を利用してコメントを追加することは、コードを読むだけでは明らかでない情報を追加する場合やテストの操作や動機に関する役立つ情報を提供する場合に最も役に立ちます。

もし、テストがバグや何かしらの問題に関連している場合は、Comment Traitの代わりにBug Traitの使用を検討する必要があります。

.bug Traitについて

テストは、開発者が書いたコードが期待通りに動作していることを確認する手段として重要です。特定のバグを再現、または修正の検証とテストを関連付けることは非常に有用です。

.bug()@Test属性のTrait引数として明示的に指定することで、関連するバグにコメントを追加することができます。引数としては、整数値や文字列が指定可能です。

@Test(
  .bug(1110), // 整数値として紐付けれる
  .bug("〇〇のバグが発生する"), // 文字列として紐付けれる
  .bug("https://someURL/bugs/1110") // URLに紐づけたと見なされる
  .bug("#143") // CI環境等にホストされている場合はGitHub Issueに紐付けたとみなされる
)
func someTest() { /* ... */ }

さらに、.bug()では第二引数としてBug.Relationshipを指定することができます。これにより、バグとテストの関係性を明示することが可能です。例として、あるテストが以前は通過していたものの、何らかのバグの影響で失敗するようになった場合、.failingBecauseOfBugを指定します。

@Test.comment("何かしらのコメント"),
  .bug("iOS17環境だと〇〇のバグが発生する", relationship: .failingBecauseOfBug)
)
func someTest() { /* ... */ }

Bug.Relationshipには以下のオプションが存在します。

  • .uncoveredBug
    • テスト実行が失敗し、バグの存在が明らかとなった場合に使用
  • .reproducesBug
    • 既に報告されているバグを再現するテストの場合に使用
  • .verifiesFix
    • バグの修正が確認され、再発がないことを示す場合に使用
  • .failingBecauseOfBug
    • 以前はテストが成功していたが、関連しないバグのために失敗している場合に使用
  • .unspecified
    • 上記のどのケースにも当てはまらない場合や、.bug()のデフォルト引数として使用

.enable Traitと.disable Traitについて

swift-testingライブラリには、特定の条件下でテストをスキップするか実行するかを制御するための.disable().enable()という2つのTraitが提供されています。

特定の条件に基づいてテストの実行をコントロールしたい場合、以下のように利用することができます。

@Test(
  .disabled() // ✅ 無条件でスキップされる
)
func someTest1() { /* ... */ }

@Test(
  .disabled("コメントを含めることが可能")
)
func someTest2() { /* ... */ }

@Test(
  .disabled(
    if: magnet.isConnected  // ✅ Bool値に基づいてテストがスキップされる
    "マグネットがくっ付いていたらテストをスキップする"
  )
)
func someTest3() { /* ... */ }

@Test(
  .enabled(
    if: !magnet.isConnected,  // ✅ Bool値に基づいてテストが有効化される
    "マグネットがくっ付いていない場合にテストを有効にする"
  )
)
func someTest4() { /* ... */ }

さらに、.disable().enable()を組み合わせて、複数の条件を一つのテストに設定することもできます。ただし、複数の条件が設定されている場合、すべての条件がパスしなければなりません。もし一つでも条件が満たされない場合、そのテストはスキップされ、スキップされた理由として最初に失敗した条件のコメントが記録されます。

@Test(
  .enabled(if: someCondition1, "テストがスキップされた場合はこのコメントが記録される"),
  .disabled(if: someCondition2, "こちらのコメントは記録されない")
  /* ... */
)
func combinedTest() { /* ... */ }

このように、swift-testingライブラリを使用することで、開発者はテストの実行条件を自由に制御し、目的に応じてテストを実行またはスキップさせることができます。


その他: 一時的なセットアップ方法について

本テストライブラリはまだSwift Package Managerに統合されていませんが、Swiftのパッケージで本ライブラリを使用し始めたい開発者のための一時的な仕組みが提供されています。

パッケージのテストターゲットにScaffolding.swiftという新しいSwiftのソースファイルを追加し、以下のコードをそのファイルに追加する必要があります。

import XCTest
import Testing

final class AllTests: XCTestCase {
  func testAll() async {
    await XCTestScaffold.runAllTests(hostedBy: self)
  }
}

まとめ

Swiftの開発環境やテスト手法は進化を続けています。XCTestの既存の制約と問題点を解決する目的で公開されたswift-testingライブラリは、Swiftのテスト環境をより良くする鍵となるでしょう。

本ライブラリを導入することにより、Objective-Cからの依存を減少させるだけでなく、Swift Concurrencyとのシームレスな統合や、より直感的なテストの記述が可能となります。また、新規のMacrosTraitsの採用により、テストの記述や管理が一段と簡潔で効果的に行うことができるようになると予測されます。

仮に、本ライブラリが将来Xcodeに正式に導入されれば、iOSアプリの開発に関連するテスト手法には大きな変化がもたらされることとなります。このような新しい動向を迅速に取り入れることで、アプリの品質向上に大きく寄与することができると期待しています。

開発環境

  • Swift compiler version info:
    • Apple Swift version 5.9 (swiftlang-5.9.0.128.106 clang-1500.0.40.1)
  • Xcode version info:
    • Xcode 15.0 Build version 15A5229m (beta 8)
  • swift-testing revision info:
    • 70f5963165929efa473522f8063e1590f85b3b9c

参考引用文献

Discussion

とんとんぼとんとんぼ

Migrating a test from XCTest がリンク切れになっているので、公式ドキュメントの Record Issues から参照した方がよさそう
内容は同じ