純粋なSwiftプロジェクトでユニットテストを実行しているかどうかをアプリに通知するにはどうすればよいですか?


86

Xcode 6.1でテストを実行する際の厄介な点の1つは、アプリ全体を実行して、ストーリーボードとルートビューコントローラーを起動する必要があることです。私のアプリでは、これはAPIデータをフェッチするいくつかのサーバー呼び出しを実行します。ただし、テストの実行時にアプリにこれを実行させたくありません。

プリプロセッサマクロがなくなったので、通常の起動ではなくテストを実行して起動されたことをプロジェクトが認識するのに最適なものは何ですか?私は通常、command+Uとボットでそれらを実行します。

擬似コード:

// Appdelegate.swift
if runningTests() {
   return
} else {
   // do ordinary api calls
}

「アプリ全体を実行して、ストーリーボードとルートビューコントローラーを起動する必要があります」というのは正しいですか?私はそれをテストしていませんが、それは私には正しくないようです。うーん...
Fogmeister

はい、アプリケーションは起動を完了し、ルートビューコントローラーのviewdidloadも実行されました
bogen 2014

ああ、テストしたばかりです。そんなことはないと思いました笑。テストで問題を引き起こしているのは、これについて何ですか?多分それを回避する別の方法がありますか?
フォグマイスター2014

テストを念頭に置いてアプリの実行を通知する必要があるため、古いプリプロセッサマクロのようなフラグは機能しますが、Swiftではサポートされていません。
bogen 2014

ええ、でもなぜそうする必要があるのですか?あなたがそれをする必要があるとあなたに思わせているのは何ですか?
フォグマイスター2014

回答:


44

副作用を回避するためにテストが実行されているかどうかを確認する代わりに、ホストアプリ自体なしでテストを実行できます。[プロジェクト設定]-> [テストターゲットの選択]-> [一般]-> [テスト]-> [ホストアプリケーション]-> [なし]を選択します。テストを実行するために必要なすべてのファイルと、通常はホストアプリターゲットに含まれているライブラリを含めることを忘れないでください。

enter image description here


1
ホストアプリケーションを削除した後、ターゲットのブリッジヘッダーを含めるにはどうすればよいですか?
Bhargav 2016年

1
これを試してみましたが、正反対のことを提案するこの答えに
到達

そうすれば、(たとえば)cloudKitをテストできるので、解決策は、applicationDidFinishLaunchingでテストしているかどうかを検出し、YESの場合は、アプリのメインクラスを割り当てずに戻ることです。
user11059 5119年

86

かつて純粋な「論理テスト」と呼ばれていたものが必要な場合、Elvindの答えは悪くありません。含まれているホストアプリケーションを実行したいが、テストが実行されているかどうかに応じてコードを条件付きで実行するか実行しない場合は、以下を使用して、テストバンドルが挿入されているかどうかを検出できます。

if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
     // Code only executes when tests are running
}

この回答で説明されている条件付きコンパイルフラグを使用して、ランタイムコストがデバッグビルドでのみ発生するようにしました。

#if DEBUG
    if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
        // Code only executes when tests are running
    }
#endif

Swift3.0を編集する

if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
    // Code only executes when tests are running
}

3
最新のXcodeでは機能しません-環境変数名が変更されたようです
amleszk 2016年

7
これがいつ機能しなくなったかは正確にはわかりませんが、Xcode 7.3XCTestConfigurationFilePathでは、の代わりに環境キーを使用していますXCInjectBundle
ospr 2016年

@osprに感謝します。Xcode7.3で動作するように回答を編集しました
Michael McGuire

@tkuichooseyouにpo ProcessInfo.processInfo.environmentはキーがありませんXCTestConfigurationFilePath。コードを共有していただけますか?これは、UITestターゲットに対して再度チェックされます
Tal Zion

@TalZion申し訳ありませんが、UITestターゲットを対象としていることに気づいていませんでした。NSClassFromString("XCTest")以下の方法を試しましたか?
tkuichooseyou 2017

44

私はこれをapplication:didFinishLaunchingWithOptionsで使用します:

// Return if this is a unit test
if let _ = NSClassFromString("XCTest") {
    return true
}

3
これは単体テストにのみ適用され、新しいXcode 7UIテストには適用されません。
ジェシー

34

その他、私の意見ではもっと簡単な方法:

スキームを編集して、ブール値を起動引数としてアプリに渡します。このような:

Xcodeで起動引数を設定する

すべての起動引数は自動的にに追加されますNSUserDefaults

これで、次のようなBOOLを取得できます。

BOOL test = [[NSUserDefaults standardUserDefaults] boolForKey:@"isTest"];

2
これは私がこれまでに見つけた中で最もクリーンな方法のようです。すべてのアプリファイルをテストターゲットに追加する必要はありません。また、"XCTestConfigurationFilePath"またはをチェックする奇妙なソリューションに依存する必要もありませんNSClassFromString("XCTest")。私はこのソリューションをグローバル関数を使用してSwiftに実装しましたfunc isRunningTests() -> Bool { return UserDefaults.standard.bool(forKey: "isRunningTests") }
Kevin Hirsch

21

テスト内で実行しているかどうかを知りたいのは完全に正当だと思います。それが役立つ理由はたくさんあります。たとえば、テストの実行では、AppDelegateのapplication-did / will-finish-launchingメソッドから早期に戻り、単体テストに密接に関係しないコードのテストをより速く開始できるようにします。それでも、他の多くの理由から、純粋な「論理」テストに進むことはできません。

私は以前、上記の@MichaelMcGuireによって説明された優れた手法を使用していました。しかし、Xcode 6.4 / iOS8.4.1の周りで機能しなくなったことに気づきました(おそらくもっと早く壊れました)。

つまり、私のフレームワークのテストターゲット内でテストを実行すると、XCInjectBundleが表示されなくなります。つまり、フレームワークをテストするテストターゲット内で実行しています。

したがって、@ Fogmeisterが提案するアプローチを利用して、私の各テストスキームは、チェックできる環境変数を設定するようになりました。

ここに画像の説明を入力してください

次に、APPSTargetConfigurationこの簡単な質問に答えることができる、というクラスにあるコードをいくつか示します。

static NSNumber *__isRunningTests;

+ (BOOL)isRunningTests;
{
    if (!__isRunningTests) {
        NSDictionary *environment = [[NSProcessInfo processInfo] environment];
        NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
        __isRunningTests = @([isRunningTestsValue isEqualToString:@"YES"]);
    }

    return [__isRunningTests boolValue];
}

このアプローチの1つの注意点は、メインのアプリスキームからテストを実行する場合、XCTestで実行できるように(つまり、テストスキームのいずれかを選択しないで)、この環境変数セットを取得できないことです。


5
「実行」に追加する代わりに、すべてのスキームの「テスト」に追加すると便利ではないでしょうか。このように、isRunningTestsはすべてのスキームで機能します。
Vishal Singh 2016

@VishalSinghはい、それはよりクリーンだと思います。そのアプローチを試してみましたか?それがあなたにとってもうまくいったかどうか私たちに知らせてください。
idStar 2016年

16
var isRunningTests: Bool {
    return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}

使用法

if isRunningTests {
    return "lena.bmp"
}
return "facebook_profile_photo.bmp"

他のいくつかの答えとは異なり、これは機能します。ありがとうございました。
n 1319

8

@Jessyと@MichaelMcGuireを組み合わせたアプローチ

(受け入れられた答えはフレームワークの開発中にあなたを助けません)

だからここにコードがあります:

#if DEBUG
        if (NSClassFromString(@"XCTest") == nil) {
            // Your code that shouldn't run under tests
        }
#else
        // unconditional Release version
#endif

1
これははるかに良いです!より短く、フレームワークと互換性があります!
blackjacx

スウィフトパッケージに取り組んでおり、これは私のために働いた
D.グレッグ

4

これが、Swift 4 / Xcode9でユニットテストに使用している方法です。それはジェシーの答えに基づいています

ストーリーボードが読み込まれるのを防ぐのは簡単ではありませんが、didFinishedLaunchingの最初にこれを追加すると、開発者にとって何が起こっているのかが非常に明確になります。

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions:
                 [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    #if DEBUG
    if let _ = NSClassFromString("XCTest") {
        // If we're running tests, don't launch the main storyboard as
        // it's confusing if that is running fetching content whilst the
        // tests are also doing so.
        let viewController = UIViewController()
        let label = UILabel()
        label.text = "Running tests..."
        label.frame = viewController.view.frame
        label.textAlignment = .center
        label.textColor = .white
        viewController.view.addSubview(label)
        self.window!.rootViewController = viewController
        return true
    }
    #endif

(明らかに、アプリを通常どおりに起動させたいUIテストでは、このようなことを行うべきではありません!)


3

ここのスキームに応じて、ランタイム引数をアプリに渡すことができます...

ここに画像の説明を入力してください

しかし、それが実際に必要かどうかは疑問です。


3

これはそれを行うための迅速な方法です。

extension Thread {
  var isRunningXCTest: Bool {
    for key in self.threadDictionary.allKeys {
      guard let keyAsString = key as? String else {
        continue
      }

      if keyAsString.split(separator: ".").contains("xctest") {
        return true
      }
    }
    return false
  }
}

そして、これはあなたがそれを使用する方法です:

if Thread.current.isRunningXCTest {
  // test code goes here
} else {
  // other code goes here
}

これが完全な記事です: https://medium.com/@theinkedengineer/check-if-app-is-running-unit-tests-the-swift-way-b51fbfd07989


:ループのために、このようSwiftilizedできることreturn threadDictionary.allKeys.anyMatch { ($0 as? String)?.split(separator: ".").contains("xctest") == true }
ロジャー・大場

2

これらのアプローチの一部はUITestで機能せず、基本的にアプリコード自体でテストしている場合(特定のコードをUITestターゲットに追加するのではなく)。

テストのsetUpメソッドで環境変数を設定することになりました。

XCUIApplication *testApp = [[XCUIApplication alloc] init];

// set launch environment variables
NSDictionary *customEnv = [[NSMutableDictionary alloc] init];
[customEnv setValue:@"YES" forKey:@"APPS_IS_RUNNING_TEST"];
testApp.launchEnvironment = customEnv;
[testApp launch];

現在、他のlaunchEnvironment値を使用していないため、これはテストにとって安全であることに注意してください。その場合は、もちろん、既存の値を最初にコピーする必要があります。

次に、アプリコードで、テスト中に一部の機能を除外する場合は、この環境変数を探します。

BOOL testing = false;
...
if (! testing) {
    NSDictionary *environment = [[NSProcessInfo processInfo] environment];
    NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
    testing = [isRunningTestsValue isEqualToString:@"YES"];
}

注-このアイデアを与えてくれたRishiGのコメントに感謝します。それを例に拡張しました。


2

私が使用していた方法は、Xcode 12ベータ1では機能しなくなりました。この質問に対するビルドベースの回答をすべて試した後、@ ODBの回答に触発されました。これは、実デバイスとシミュレータの両方で機能する非常に単純なソリューションのSwiftバージョンです。また、かなり「リリースプルーフ」である必要があります。

テストセットアップに挿入:

let app = XCUIApplication()
app.launchEnvironment.updateValue("YES", forKey: "UITesting")
app.launch()

アプリに挿入:

let isTesting: Bool = (ProcessInfo.processInfo.environment["UITesting"] == "YES")

それを使用するには:

    if isTesting {
        // Only if testing
    } else {
        // Only if not testing
    }

0

私のために働いた:

Objective-C

[[NSProcessInfo processInfo].environment[@"DYLD_INSERT_LIBRARIES"] containsString:@"libXCTTargetBootstrapInject"]

迅速: ProcessInfo.processInfo.environment["DYLD_INSERT_LIBRARIES"]?.contains("libXCTTargetBootstrapInject") ?? false


0

最初にテスト用の変数を追加します。

ここに画像の説明を入力してください

そしてそれをあなたのコードで使用してください:

 if ProcessInfo.processInfo.environment["IS_UNIT_TESTING"] == "1" {
                 // Code only executes when tests are running
 } 

0

どうやらXcode12XCTestBundlePathではXCTestConfigurationFilePath、新しいものを使用している場合ではなく、環境キーで検索する必要がありますXCTestPlan

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.