XCode 6.1でテストを実行する際の厄介な点の1つは、アプリ全体を実行して、ストーリーボードとルートのviewControllerを起動する必要があることです。私のアプリでは、APIデータを取得するサーバー呼び出しをいくつか実行します。ただし、テストを実行するときにアプリにこれを行わせたくありません。
プリプロセッサマクロがなくなったので、通常の起動ではなくテストを実行して起動したことをプロジェクトが認識するのに最適なものは何ですか?通常はCMD + Uを使用してボットで実行します。
擬似コードは次のとおりです。
// Appdelegate.Swift
if runningTests() {
return
} else {
// do ordinary api calls
}
副作用を避けるためにテストが実行されているかどうかを確認する代わりに、ホストアプリ自体なしでテストを実行できます。 [プロジェクトの設定]->テスト対象を選択->一般->テスト->ホストアプリケーション-> [なし]を選択します。テストを実行するために必要なすべてのファイルと、通常ホストアプリターゲットによって含まれるライブラリを含めることを忘れないでください。
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
if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
// Code only executes when tests are running
}
Application:didFinishLaunchingWithOptionsでこれを使用します:
// Return if this is a unit test
if let _ = NSClassFromString("XCTest") {
return true
}
テスト内で実行しているかどうかを知りたいのは完全に合法だと思います。これが役立つ理由は数多くあります。たとえば、テストの実行中に、App Delegateのapplication-did/will-finish-launchingメソッドから早く戻り、ユニットテストに関係のないコードのテストをより早く開始します。しかし、他の多くの理由により、純粋な「論理」テストを行うことはできません。
上記の@Michael McGuireによって説明された優れた手法を使用していました。ただし、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でできるように(つまり、テストスキームの1つを選択しないで)メインアプリスキームからテストを実行する場合、この環境変数は設定されないことです。
var isRunningTests: Bool {
return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}
if isRunningTests {
return "lena.bmp"
}
return "facebook_profile_photo.bmp"
ユニットテストにSwift 4/Xcode 9で使用している方法です。これは Jesseの答え に基づいています。
ストーリーボードが読み込まれるのを防ぐのは簡単ではありませんが、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テストでは、明らかにこのようなことはすべきではありません!)
ここでスキームに応じてアプリにランタイム引数を渡すことができます...
しかし、私はそれが実際に必要かどうか疑問に思うでしょう。
@Jessyと@Michael McGuireの組み合わせアプローチ
(フレームワークの開発中に受け入れられた答えはあなたを助けません)
コードは次のとおりです。
#if DEBUG
if (NSClassFromString(@"XCTest") == nil) {
// Your code that shouldn't run under tests
}
#else
// unconditional Release version
#endif
これらのアプローチのいくつかは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のコメントに感謝します。これを例に拡張しました。