記事


XCTest からのテストの移行


XCTest を使用して記述された既存のテストメソッドまたはテストクラスを移行します。





概観


テストライブラリは XCTest とほぼ同じ機能を提供しますが、テスト関数と型の宣言には独自の構文を使用します。代わりにここでは、XCTest を基礎としたコンテンツをテストライブラリを使用するように変換する方法を学びます。



テストライブラリをインポートする


XCTest とテストライブラリは異なるモジュールから利用できます。XCTest モジュールをインポートする代わりに、Testing モジュールをインポートしてください。


// Before
import XCTest

// After
import Testing





1 つのソースファイルには、XCTest で記述されたテストと、テストライブラリで記述された他のテストの両方を含めることができます。ソースファイルにテスト内容が混在している場合は、XCTest と Testing の両方をインポートしてください。


テストクラスを変換する


XCTest は、関連するテストメソッドのセットをテストクラスにグループ化します。テストクラスは、XCTest フレームワークが提供する XCTestCase クラスを継承するクラスです。テストライブラリでは、テスト関数が型のインスタンスメンバーである必要はありません。代わりに、テスト関数は、自由関数またはグローバル関数、あるいは型の static メンバーまたは class メンバーにすることができます。


テスト関数をグループ化したい場合は、Swift の型に配置することで実現できます。テストライブラリでは、このような型をスイートと呼びます。これらの型はクラスである必要は なくXCTestCase を継承することもありません。


XCTestCase のサブクラスをスイートに変換するには、XCTestCase の適合性を削除します。また、Swift コンパイラが並行処理の安全性をより適切に確保できるため、クラスではなく Swift の構造体または actor (アクター) を使用することが一般的に推奨されます。


// Before
class FoodTruckTests: XCTestCase {
  ...
}

// After
struct FoodTruckTests {
  ...
}







スイートの詳細と、スイートの宣言およびカスタマイズする方法については、スイートの型によるテスト関数の整理 を参照してください。



セットアップとティアダウン関数を変換する


XCTest では、setUp() および tearDown() 関数群を使用して、テストの前後にコードを実行するようにスケジュール設定できます。テストライブラリを使用してテストを作成する場合は、代わりに init() または deinit、あるいはその両方を実装してください。


// Before
class FoodTruckTests: XCTestCase {
  var batteryLevel: NSNumber!
  override func setUp() async throws {
    batteryLevel = 100
  }
  ...
}

// After
struct FoodTruckTests {
  var batteryLevel: NSNumber
  init() async throws {
    batteryLevel = 100
  }
  ...
}











asyncthrows の使用はオプションです。teardown が必要な場合は、テストスイートを構造体ではなくクラスまたはアクターとして宣言し、deinit を実装してください。


// Before
class FoodTruckTests: XCTestCase {
  var batteryLevel: NSNumber!
  override func setUp() async throws {
    batteryLevel = 100
  }
  override func tearDown() {
    batteryLevel = 0 // drain the battery
  }
  ...
}

// After
final class FoodTruckTests {
  var batteryLevel: NSNumber
  init() async throws {
    batteryLevel = 100
  }
  deinit {
    batteryLevel = 0 // drain the battery
  }
  ...
}














テストメソッドの変換


テストライブラリは、個々のテストを関数として表現します。これは XCTest での表現方法に似ています。ただし、テスト関数の宣言構文は異なります。XCTest では、テストメソッドはテストクラスのメンバーでなければならず、名前は test で始まらなければなりません。テストライブラリでは、テスト関数に特定の名前を付ける必要はありません。代わりに、@Test 属性の存在によってテスト関数を識別します。


// Before
class FoodTruckTests: XCTestCase {
  func testEngineWorks() { ... }
  ...
}

// After
struct FoodTruckTests {
  @Test func engineWorks() { ... }
  ...
}








XCTest と同様に、テストライブラリを使用すると、テスト関数を async、throws、または async-throws としてマークし、グローバルアクターに分離することができます (たとえば、@MainActor 属性を使用して)。


注意

XCTest はデフォルトでメインアクター上で同期テストメソッドを実行しますが、テストライブラリはすべてのテスト関数を任意のタスク上で実行します。テスト関数をメインスレッド上で実行しなければならない場合は、@MainActor を使用してメインアクターに分離するか、スレッド依存のコードを MainActor.run(resultType:body:) の呼び出し内で実行してください。


テスト関数とその宣言およびカスタマイズ方法の詳細については、テスト関数の定義 を参照してください。



期待値と結果を確認する


XCTest は、テスト要件をアサートするために約 40 個の関数群を使用します。これらの関数は総称して XCTAssert() と呼ばれます。テストライブラリには、expect(_:_:sourceLocation:)require(_:_:sourceLocation:) という 2 つの代替関数が用意されています。どちらも XCTAssert() と同様に動作しますが、require(_:_:sourceLocation:) は条件が満たされない場合にエラーを throws します。


// Before
func testEngineWorks() throws {
  let engine = FoodTruck.shared.engine
  XCTAssertNotNil(engine.parts.first)
  XCTAssertGreaterThan(engine.batteryLevel, 0)
  try engine.start()
  XCTAssertTrue(engine.isRunning)
}

// After
@Test func engineWorks() throws {
  let engine = FoodTruck.shared.engine
  try #require(engine.parts.first != nil)
  #expect(engine.batteryLevel > 0)
  try engine.start()
  #expect(engine.isRunning)
}












オプション値を確認する


XCTest には、オプション値が nil かどうかをテストし、nil の場合はエラーを throws する関数 XCTUnwrap() もあります。テストライブラリを使用する場合は、オプション式を require(_:_:sourceLocation:) で開封できます。


// Before
func testEngineWorks() throws {
  let engine = FoodTruck.shared.engine
  let part = try XCTUnwrap(engine.parts.first)
  ...
}

// After
@Test func engineWorks() throws {
  let engine = FoodTruck.shared.engine
  let part = try #require(engine.parts.first)
  ...
}










記録の問題


XCTest には、テストを即座に無条件に失敗させる関数 XCTFail() があります。この関数は、言語の構文上 XCTAssert() 関数を使用できない場合に便利です。テストライブラリを使用して無条件の問題を記録するには、record(_:sourceLocation:) 関数を使用します。


// Before
func testEngineWorks() {
  let engine = FoodTruck.shared.engine
  guard case .electric = engine else {
    XCTFail("Engine is not electric")
    return
  }
  ...
}

// After
@Test func engineWorks() {
  let engine = FoodTruck.shared.engine
  guard case .electric = engine else {
    Issue.record("Engine is not electric")
    return
  }
  ...
}













以下の表には、さまざまな XCTAssert() 関数と、テストライブラリ内の同等の関数のリストが含まれています。


XCTestSwift 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() }
XCTAssertThrowsError(try f()) { error in … }let error = #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("…")


テストライブラリには、XCTAssertEqual(_:_:accuracy:_:file:line:) に相当する関数は用意されていません。指定された精度内で 2 つの数値を比較するには、swift-numericsisapproximatelyEqual() を使用してください。



テスト失敗後に継続または停止する


XCTestCase サブクラスのインスタンスは、continueAfterFailure プロパティを false に設定することで、失敗発生後にテストの実行を停止することができます。XCTest は、失敗発生時に Objective-C 例外をスローすることで、影響を受けるテストを停止します。


注意

Apple 以外のプラットフォームで swift-corelibs-xctest ライブラリを使用する場合、continueAfterFailure は完全にはサポートされません。


Swift スタックフレームを通じてスローされた例外の動作は未定義です。Swift の async 関数を通じて例外がスローされた場合、通常はプロセスが異常終了し、他のテストの実行が妨げられます。


テストライブラリは、テスト関数を停止するために例外を使用しません。代わりに、失敗した場合に Swift エラーをスローする require(_:_:sourceLocation:) マクロを使用します。


// Before
func testTruck() async {
  continueAfterFailure = false
  XCTAssertTrue(FoodTruck.shared.isLicensed)
  ...
}

// After
@Test func truck() throws {
  try #require(FoodTruck.shared.isLicensed)
  ...
}











continueAfterFailure または require(_:_:sourceLocation:) のいずれかを使用すると、失敗したテストメソッドまたはテスト関数の後に他のテストが引き続き実行されます。



非同期動作を検証する


XCTestには、ある非同期条件を表すクラス XCTestExpectation があります。このクラス (または XCTestKeyPathExpectation などのサブクラス) のインスタンスは、イニシャライザまたは XCTestCase のコンビニエンスメソッドを使用して作成します。期待値で表された条件が発生すると、開発者はその期待値を 満たします。同時に、開発者は XCTWaiter のインスタンスまたは XCTestCase のコンビニエンスメソッドを使用して、期待値が満たされるのを 待機 します。


可能な限り、Swift の並行処理を使用して非同期条件を検証することをお勧めします。例えば、非同期の Swift 関数の結果を判定する必要がある場合は、await を使用して待機できます。完了ハンドラを受け取るものの await を使用しない関数の場合は、Swift の continuation を使用して呼び出しを async- (非同期) 対応の呼び出しに変換できます。


一部のテスト、特に非同期配信イベントをテストするものは、Swift の並行処理に容易に移行できません。テストライブラリには、これらのテストを実装するための 確認 関数と呼ばれる機能が用意されています。Confirmation のインスタンスは、confirmation(_:expectedCount:isolation:sourceLocation:_:) および confirmation(_:expectedCount:isolation:sourceLocation:_:) 関数のスコープ内で作成および使用されます。


Confirmation (確認) 関数は XCTest の期待値 API と同様に似ていますが、条件が満たされるのを待つ間、呼び出し元をブロックしたり停止させたりしません。その代わりに、confirmation() が返される前に要件が 確認 されること (期待値が 満たされる ことと同等) が期待され、そうでない場合は問題が記録されます。


// Before
func testTruckEvents() async {
  let soldFood = expectation(description: "…")
  FoodTruck.shared.eventHandler = { event in
    if case .soldFood = event {
      soldFood.fulfill()
    }
  }
  await Customer().buy(.soup)
  await fulfillment(of: [soldFood])
  ...
}

// After
@Test func truckEvents() async {
  await confirmation("…") { soldFood in
    FoodTruck.shared.eventHandler = { event in
      if case .soldFood = event {
        soldFood()
      }
    }
    await Customer().buy(.soup)
  }
  ...
}















デフォルトでは、XCTestExpectation は正確に 1 回だけ満たされることを期待し、満たされない場合、または複数回満たされた場合は、現在のテストで問題を記録します。Confirmation も同様の動作をし、デフォルトでは正確に 1 回だけ確認されることを期待します。期待値が満たされる回数は、expectedFulfillmentCount プロパティを設定することで構成できます。また、confirmation(_:expectedCount:isolation:sourceLocation:_:)expectedCount 引数に値を渡すことでも同様のことができます。


XCTestExpectation には assertForOverFulfill プロパティがあり、これを false に設定すると、期待値が期待値よりも多く満たされてもテスト失敗とはなりません。確認を使用する場合、confirmation(_:expectedCount:isolation:sourceLocation:_:) に範囲を渡すことで、少なくとも 一定回数は確認しなければならないことを示すことができます。


// Before
func testRegularCustomerOrders() async {
  let soldFood = expectation(description: "…")
  soldFood.expectedFulfillmentCount = 10
  soldFood.assertForOverFulfill = false
  FoodTruck.shared.eventHandler = { event in
    if case .soldFood = event {
      soldFood.fulfill()
    }
  }
  for customer in regularCustomers() {
    await customer.buy(customer.regularOrder)
  }
  await fulfillment(of: [soldFood])
  ...
}

// After
@Test func regularCustomerOrders() async {
  await confirmation(
    "…",
    expectedCount: 10...
  ) { soldFood in
    FoodTruck.shared.eventHandler = { event in
      if case .soldFood = event {
        soldFood()
      }
    }
    for customer in regularCustomers() {
      await customer.buy(customer.regularOrder)
    }
  }
  ...
}




















下限値を持つ範囲式 (つまり、RangeExpression<Int>Sequence<Int> の両方の型に準拠する式) は、confirmation(_:expectedCount:isolation:sourceLocation:_:) で使用できます。確認回数の下限値を指定しないと、確認回数が 0 回の場合に問題を記録するかどうかをテストライブラリが判断できないため、下限値を指定しなければなりません。



テストを実行するかどうかを制御する




@@@@@@@@@@@@@@
6/24ここまで
@@@@@@@@@@@@@@





以下も見よ


要点


テスト関数の定義

コードが正しく動作していることを検証するためのテスト関数を定義します。


スイートの型によるテスト関数の整理

テストをテスト スイートに整理します。


macro Test(String?, any TestTrait...)

テストを宣言します。


struct Test

テストまたはスイートを表す型。


macro Suite(String?, any SuiteTrait...)

テストスイートを宣言します。














トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ