記事


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 回の場合に問題を記録するかどうかをテストライブラリが判断できないため、下限値を指定しなければなりません。



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


XCTest を使用する場合、XCTSkip エラー型を throw することで、テスト関数の残りの部分をバイパスできます。また、XCTSkipIf() 関数と XCTSkipUnless() 関数を使用して、同じアクションを条件付きで実行することもできます。テストライブラリでは、ConditionTrait 特性の型を使用することで、テスト関数またはテストスイート全体を実行前にスキップできます。テストスイートまたはテスト関数にこの特性型のインスタンスを注釈することで、実行の有無を制御できます。


// Before
class FoodTruckTests: XCTestCase {
  func testArepasAreTasty() throws {
    try XCTSkipIf(CashRegister.isEmpty)
    try XCTSkipUnless(FoodTruck.sells(.arepas))
    ...
  }
  ...
}

// After
@Suite(.disabled(if: CashRegister.isEmpty))
struct FoodTruckTests {
  @Test(.enabled(if: FoodTruck.sells(.arepas)))
  func arepasAreTasty() {
    ...
  }
  ...
}













既知の問題に注釈を付ける


テストには、時々、あるいは常にテストの成功を妨げる既知の問題がある場合があります。XCTest を使用して記述した場合、そのようなテストは XCTest とそのインフラストラクチャに、その問題によってテストが失敗しないことを伝えるために XCTestExpectFailure(_:options:failingBlock:) を呼び出すことができます。テストライブラリには、同期版と非同期版の両方を持つ同等の関数が用意されています。


  • withKnownIssue(_:isIntermittent:sourceLocation:_:)

  • withKnownIssue(_:isIntermittent:isolation:sourceLocation:_:)

  • この関数を使用すると、テストのセクションに既知の問題があることを注釈付けできます。


    // Before
    func testGrillWorks() async {
      XCTExpectFailure("Grill is out of fuel") {
        try FoodTruck.shared.grill.start()
      }
      ...
    }
    

    // After
    @Test func grillWorks() async {
      withKnownIssue("Grill is out of fuel") {
        try FoodTruck.shared.grill.start()
      }
      ...
    }
    











    注意

    XCTest 関数 XCTExpectFailure(_:options:) はクロージャを取らず、テストの残りの部分に影響を与えますが、テストライブラリ内に直接対応する関数はありません。テスト全体に既知の問題があるとマークするには、テスト本体を withKnownIssue() 呼び出しで囲みます。


    テストが断続的に失敗する場合は、XCTExpectFailure(_:options:failingBlock:) の呼び出しを non-strict としてマークできます。テストライブラリを使用する場合は、代わりに既知の問題が intermittent であると指定します。


    // Before
    func testGrillWorks() async {
      XCTExpectFailure(
        "Grill may need fuel",
        options: .nonStrict()
      ) {
        try FoodTruck.shared.grill.start()
      }
      ...
    }
    

    // After
    @Test func grillWorks() async {
      withKnownIssue(
        "Grill may need fuel", 
        isIntermittent: true
      ) {
        try FoodTruck.shared.grill.start()
      }
      ...
    }
    














    XCTExpectFailure() を呼び出す際に、追加のオプションを指定できます。


  • isEnabledfalse に設定すると、既知の問題の一致をスキップできます (たとえば、特定の問題が特定の条件下でのみ発生する場合など)。

  • issueMatcher をクロージャに設定することで、特定の問題のみを既知の問題としてマークし、その他の問題はテスト失敗として記録することができます。

  • テストライブラリには、同様の動作をする、追加の引数を取る withKnownIssue() のオーバーロードが含まれています。


  • withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:)

  • withKnownIssue(_:isIntermittent:isolation:sourceLocation:_:when:matching:)

  • 既知の問題の一致を条件付きで有効にしたり、特定の種類の問題のみを一致させたりするには、以下の手順を実行します。


    // Before
    func testGrillWorks() async {
      let options = XCTExpectedFailure.Options()
      options.isEnabled = FoodTruck.shared.hasGrill
      options.issueMatcher = { issue in
        issue.type == thrownError
      }
      XCTExpectFailure(
        "Grill is out of fuel",
        options: options
      ) {
        try FoodTruck.shared.grill.start()
      }
      ...
    }
    

    // After
    @Test func grillWorks() async {
      withKnownIssue("Grill is out of fuel") {
        try FoodTruck.shared.grill.start()
      } when: {
        FoodTruck.shared.hasGrill
      } matching: { issue in
        issue.error != nil 
      }
      ...
    }
    



















    テストを順番に実行する


    デフォルトでは、テストライブラリはスイート内のすべてのテストを並列で実行します。XCTest のデフォルトの動作では、スイート内の各テストは順次実行されます。テストでグローバル変数などの共有状態を使用している場合、テストを並列で実行すると、信頼性の低いテスト結果など、予期しない動作が発生する可能性があります。


    テストスイートに serialized 注釈を付けると、スイート内のテストが順次実行されます。


    // Before
    class RefrigeratorTests : XCTestCase {
      func testLightComesOn() throws {
        try FoodTruck.shared.refrigerator.openDoor()
        XCTAssertEqual(FoodTruck.shared.refrigerator
        .lightState, .on)
      }
      
      func testLightGoesOut() throws {
        try FoodTruck.shared.refrigerator.openDoor()
        try FoodTruck.shared.refrigerator.closeDoor()
        XCTAssertEqual(FoodTruck.shared.refrigerator
        .lightState, .off)
      }
    }
    

    // After
    @Suite(.serialized)
    class RefrigeratorTests {
      @Test func lightComesOn() throws {
        try FoodTruck.shared.refrigerator.openDoor()
        #expect(FoodTruck.shared.refrigerator.lightState == .on)
      }
      
      @Test func lightGoesOut() throws {
        try FoodTruck.shared.refrigerator.openDoor()
        try FoodTruck.shared.refrigerator.closeDoor()
        #expect(FoodTruck.shared.refrigerator.lightState == .off)
      }
    }
    



















    詳細については、テストを順次または並列に実行する (Running tests serially or in parallel) を参照してください。



    価値を付与する


    XCTest では、任意のデータ、ファイル、プロパティリスト、コード化可能なオブジェクト、画像、その他テスト失敗時に利用可能な情報を表す XCTAttachment のインスタンスを作成できます。Swift Testing には、ほぼ同じ目的を果たす Attachment 型があります。


    テスト実行の出力にテストの値を付与するには、その値が Attachable プロトコルに準拠していなければなりません。テストライブラリは、様々な標準ライブラリおよび Foundation 型に対してデフォルトの準拠を提供します。


    別の型の値を付与したい場合、その型がすでに Encodable または NSSecureCoding に準拠しているときは、テストライブラリは Foundation をインポートするときに自動的にデフォルトの実装を提供します。


    // Before
    import Foundation
    
    class Tortilla: NSSecureCoding { /* ... */ }
    
    func testTortillaIntegrity() async {
      let tortilla = Tortilla(diameter: .large)
      ...
      let attachment = XCTAttachment(
        archivableObject: tortilla
      )
      self.add(attachment)
    }
    

    // After
    import Foundation
    
    struct Tortilla: Codable, Attachable { /* ... */ }
    
    @Test func tortillaIntegrity() async {
      let tortilla = Tortilla(diameter: .large)
      ...
      Attachment.record(tortilla)
    }
    

















    もしあなたに Encodable または NSSecureCoding に準拠していない (または準拠できない) 型がある場合、またはテストに付与するときにシリアル化する方法を細かく制御したい場合は、独自の withUnsafeBytes(for:_:) 実装を提供できます。





    以下も見よ


    関連する文書


    テスト関数の定義

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


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

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


    期待と確認 (Expectations and confirmations)

    テストで期待される値、結果、非同期イベントを確認します。


    既知の問題 (Known issues)

    テストを実行するときに、問題を既知としてマークします。



    要点


    テスト関数の定義

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


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

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


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

    テストを宣言します。


    struct Test

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


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

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














    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ