[Swift] テストの実行時にプロダクトコードを実行しないようにする

テストを書いて、XCTest を実行してみるとユニットテスト以外にもアプリが起動しますよね。

他の言語のユニットテストフレームワークだとそんなことはないのですが、XCTest の場合テスト実行とともに application(application:didFinishLaunchingWithOptions) が呼ばれます。

テスト実行時にアプリの起動時のコードも実行されると無駄なログが発生したり、テストを正しく実行できません。


テスト実行中かどうかを判定する

テストの実行中かどうかはテストの実行環境にのみ存在する Class があるかどうかで判定できます。

テストの実行環境にのみ存在する Class とは XCTest Class などがあります。

    func isTesting() -> Bool {

return NSClassFromString("XCTest") != nil // nil じゃなかったらテスト実行中
}


2016/08/21 23:48追記

※これはXcode7まで

@mono0926 さんからコメントいただきました。↑の例とは別に判定する方法があるそうです。

    func isTesting() -> Bool {

return NSProcessInfo.processInfo().environment["XCInjectBundle"] != nil
}


2016/10/28 23:38追記

ProcessInfoを使う場合 Xcode8 から判別方法が変わったようです。

    func isTesting() -> Bool {

return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}


テスト実行中の場合処理をスキップする

この判定を application(application:didFinishLaunchingWithOptions) の先頭において、もしテスト中だと何もしないコードを入れます。本番環境に(テスト専用の)不要なコードが入るのはイヤなので #if DEBUG など、リリース時にはコードが含まれないようにするといいと思います。

class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
#if DEBUG
guard !isTesting() else { // テスト実行中なら
window?.rootViewController = UIViewController()
return true
}
#endif
}
}

extension AppDelegate {
func isTesting() -> Bool {
return NSClassFromString("XCTest") != nil
}
}