web-dev-qa-db-ja.com

XCTestを実行する前に1回限りのセットアップコードを実行する方法

次の問題があります。すべてのテストクラスが実行される前にコードを実行したい。たとえば、実行中にゲームでSoundEngineシングルトンを使用するのではなく、SilentSoundEngineを使用するようにします。すべてのテストではなく、SilentSoundEngineを一度アクティブにしたいと思います。すべてのテストは次のようになります。

class TestBasketExcercise : XCTestCase {        
    override func setUp() {
        SilentSoundEngine.activate () // SoundEngine is a singleton
    }
    // The tests 
}

-編集-ほとんどの答えは、TestCaseにカスタムスーパークラスを提供することを目的としています。すべてのテストを実行する必要がある環境を提供する、より一般的でクリーンな方法を探しています。テスト用の機能のような「メイン」機能/ Appdelegateはありませんか?

34
Raymond

TL; DR:
述べたように、 here 、テスト対象のInfo.plistでNSPrincipalClassを宣言する必要があります。 「XCTestがテストバンドルのロード時にそのクラスの単一インスタンスを自動的に作成する」ため、このクラスのinit内ですべてのワンタイムセットアップコードを実行します。したがって、ロード時にすべてのワンタイムセットアップコードが1回実行されます。テストバンドル。


もう少し冗長:

編集のアイデアに最初に答えるには:

テストは実行中のメインターゲットに挿入されるため、テストバンドルにはmain()はありません。したがって、main()にワンタイムセットアップコードを追加する必要があります。コンパイル時(または少なくともランタイム)を使用してメインターゲットをテストの実行に使用するかどうかを確認します。このチェックなしでは、ターゲットを正常に実行するときにSilentSoundEngineをアクティブ化するリスクがありますが、クラス名はこのサウンドエンジンが音を出さないことを意味するため、これは望ましくありません。 :)

ただし、AppDelegateのような機能があります。答えの最後に行きます(せっかちな場合は、「別の(よりXCTest固有の)アプローチ」というヘッダーの下にあります)。


それでは、この質問を2つの中心的な問題に分けましょう。

  1. 実行したいコードが正確に1回テストの実行中に実際に実行されていることを確認するにはどうすればよいですか完全に1回テスト
  2. Where そのコードを実行する必要があるので、 gいハック のように感じないので、考えずに必要なことを覚えなくても機能します新しいテストスイートを作成するたびの手順

ポイント1について:

@Martin Rが この回答 へのコメントで正しく言及しているように、Swift 1.2の時点で_+load_をオーバーライドすることはできません:D)、およびdispatch_once()はSwift 3では使用できなくなりました。


一つのアプローチ

とにかく_dispatch_once_を使用しようとすると、Xcode(> = 8)は常に非常に賢く、代わりに遅延初期化されたグローバルを使用することを推奨します。もちろん、globalという用語は、誰もが恐怖とパニックに夢中になる傾向がありますが、もちろん、それらをprivate/fileprivateにすることでスコープを制限できます(ファイルレベルの宣言でも同じです)。名前空間を汚染しないでください。

私見、彼らは実際にかなり素敵なパターンです(それでも、用量は毒を作る...)、これは次のように見えることができます:

_private let _doSomethingOneTimeThatDoesNotReturnAResult: Void = {
    print("This will be done one time. It doesn't return a result.")
}()

private let _doSomethingOneTimeThatDoesReturnAResult: String = {
    print("This will be done one time. It returns a result.")
    return "result"
}()

for i in 0...5 {
    print(i)
    _doSomethingOneTimeThatDoesNotReturnAResult
    print(_doSomethingOneTimeThatDoesReturnAResult)
}
_

これは印刷します:

これは1回行われます。結果を返しません。
これは1回行われます。結果を返します。
0
結果
1
結果
2
結果
3
結果
4
結果
5
結果

サイドノート:興味深いことに、プライベートレットはループが開始する前に評価されます。そうでない場合、0が最初の印刷であることがわかります。ループをコメントアウトしても、最初の2行が出力されます(つまり、letを評価します)。
ただし、 here および here のように、グローバルは通常どこかで最初に参照されたときに初期化されるため、これはプレイグラウンド固有の動作であると思います。ループをコメントアウトするときに評価されるべきではありません。


別の(よりXCTest固有の)アプローチ

(これは実際にポイント1と2の両方を解決します...)

Cupertinoの会社が here と述べているように、1回限りの事前テストセットアップコードを実行する方法があります。

これを実現するには、ダミーのセットアップクラス(おそらくTestSetupと呼びますか?)を作成し、1回限りのセットアップコードをinitに入れます。

_class TestSetup: NSObject {
    override init() {
        SilentSoundEngine.activate()
    }
}
_

クラスはNSObjectから継承する必要があります。Xcodeは_+new_を使用して「そのクラスの単一インスタンス」のインスタンス化を試みるため、クラスが純粋な_の場合Swiftクラス、これは起こります:

_*** NSForwarding: warning: object 0x11c2d01e0 of class 'YourTestTargetsName.TestSetup' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector +[YourTestTargetsName.TestSetup new]
_

次に、このクラスをテストバンドルのInfo.plistファイルでPrincipalClassとして宣言します。 Declaring the PrincipalClass in Info.plist

完全修飾クラス名(つまり、TestSetupと比較してYourTestTargetsName.TestSetup)を使用する必要があるため、クラスはXcodeによって検出されます(ありがとう、 zneak ...)。

XCTestObservationCenterのドキュメントに記載されているように、「XCTestはテストバンドルのロード時にそのクラスの単一インスタンスを自動的に作成します」ので、テストバンドルのロード時にすべてのワンタイムセットアップコードがTestSetupのinitで実行されます。

44
Jan Nash

From テストクラスとメソッドの記述

オプションで、クラスのセットアップ用にカスタマイズされたメソッドを追加できます(+ (void)setUp)および分解(+ (void)tearDown)も同様に、クラス内のすべてのテストメソッドの前後に実行されます。

In Swiftこれはclassメソッドになります:

override class func setUp() {
    super.setUp()
    // Called once before all tests are run
}

override class func tearDown() {
    // Called once after all tests are run
    super.tearDown()
}
56
Martin R

テストケースのベースとなるスーパークラスを構築する場合、スーパークラスでユニバーサルセットアップを実行し、サブクラスで必要な特定のセットアップを実行できます。 SwiftよりもObj-Cに精通しており、これをまだテストする機会はありませんでしたが、これは近いはずです。

// superclass
class SuperClass : XCTestCase {        
    override func setUp() {
        SilentSoundEngine.activate () // SoundEngine is a singleton
    }
}

// subclass
class Subclass : Superclass {
    override func setUp() {
        super.setup()
    }
}
3
tsmith1024