47

我遇到了一个我以前没有遇到过的奇怪问题。

当您执行 cmd+U 来运行单元测试(例如 OCUnit)时,它实际上是否调用了 main.m、新建 appDelegate 并运行应用程序,就好像您按下了 cmd+R 一样?

我只问是因为我在这个 DataLayer 后面使用 CoreData。我在测试中成功地模拟了 DataLayer,但是一旦我实现了一个实际调用 CoreData 的 getAll 方法,app/xcode 就会抛出一个关于托管对象模型不能为 nil 的异常。我理解,但我并不是要真正新建 DataLayer 类,我在 mainviewcontroller loadView 方法中设置了一个断点,它调用 DataLayer getAll 方法。与测试无关,因为这是一个模拟对象,但它显然是在调用真实实例。

回到我的问题,当按下 cmd+U 时,它是否也先运行应用程序然后运行测试?

4

10 回答 10

65

该应用程序实际上正在运行,但您可以使用一个技巧来阻止它运行。

int main(int argc, char* argv[]) {
    int returnValue;

    @autoreleasepool {
        BOOL inTests = (NSClassFromString(@"SenTestCase") != nil
                     || NSClassFromString(@"XCTest") != nil);    

        if (inTests) {
            //use a special empty delegate when we are inside the tests
            returnValue = UIApplicationMain(argc, argv, nil, @"TestsAppDelegate");
        }
        else {
            //use the normal delegate 
            returnValue = UIApplicationMain(argc, argv, nil, @"AppDelegate");
        }
    }

    return returnValue;
}
于 2013-03-31T01:15:27.440 回答
20

这是使用 XCTest 的 Sulthan 答案的变体,这是 XCode 5 生成的测试类的默认设置。


int main(int argc, char * argv[])
{
    @autoreleasepool {
        BOOL runningTests = NSClassFromString(@"XCTestCase") != nil;
        if(!runningTests)
        {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
        else
        {
            return UIApplicationMain(argc, argv, nil, @"TestAppDelegate");
        }
    }
}

这进入 main.m,它应该在标准项目布局中的Supporting Files下。

然后在您的测试目录中添加:

TestAppDelegate.h


#import <Foundation/Foundation.h>

@interface TestAppDelegate : NSObject<UIApplicationDelegate>
@end

TestAppDelegate.m


#import "TestAppDelegate.h"

@implementation TestAppDelegate
@end
于 2013-12-14T20:42:43.917 回答
11

在 Swift 中,我更喜欢绕过内部的正常执行路径application: didFinishLaunchingWithOptions

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    guard normalExecutionPath() else {
        window = nil
        return false
    }

    // regular setup

    return true
}

private func normalExecutionPath() -> Bool {
    return NSClassFromString("XCTestCase") == nil
}

里面的代码guard将删除从情节提要创建的任何视图。

于 2015-12-20T17:19:42.377 回答
5

如果您使用的是 Swift(您可能没有main.c),则必须执行以下步骤:

@UIApplicationMain1 :删除AppDelegate.swift

2:创建一个空TestingAppDelegate.swift

import UIKit
class TestingAppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
}

3:创建一个名为main.swift

import Foundation
import UIKit

let isRunningTests = NSClassFromString("XCTestCase") != nil

if isRunningTests {
   UIApplicationMain(C_ARGC, C_ARGV, nil, NSStringFromClass(TestingAppDelegate))
} else {
   UIApplicationMain(C_ARGC, C_ARGV, nil, NSStringFromClass(AppDelegate))
}
于 2015-06-26T10:38:51.437 回答
2

我找到了另一个解决问题的方法:

int main(int argc, char * argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, ({
            ![NSProcessInfo processInfo].environment[@"XCTestConfigurationFilePath"] ?
            @"AppDelegate" :
            nil;                
        }));
    }
}

从这里:http: //qualitycoding.org/app-delegate-for-tests/#comment-63984

于 2016-04-21T07:41:38.660 回答
1

是的,您的测试目标将对应用目标具有目标依赖关系,因此当您按 Cmd+U 或 Cmd+Shift+U 时,将构建应用目标。

于 2013-03-30T20:23:54.577 回答
1

使用 xCode 7 和 xCtool

xctool能够在不运行应用程序的情况下执行单元测试。

为了让这个工作,

1. 更新目标设置以在没有主机应用程序的情况下运行。

选择您的项目 --> 然后测试目标 --> 将宿主应用程序设置为无。

在此处输入图像描述

2. 安装 xctool ,如果你没有的话。

brew install xctool

3. 使用带有 xctool 的终端运行测试。

xctool -workspace yourWorkspace.xcworkspace -scheme yourScheme run-tests -sdk iphonesimulator
于 2016-05-25T09:40:25.480 回答
1

上面的优秀答案建议在运行时动态更改应用程序委托。

我做的小修改是通过查询来检测单元测试运行NSProcessInfo。好处是你不需要有一个可以检测到的类来查看单元测试是否正在运行。

    int main(int argc, char * argv[])
    {
        // Put your App delegate class here.
        const Class appDelegateClass = [ATAppDelegate class];

        NSDictionary *const environmentDictionary =
        [[NSProcessInfo processInfo] environment];

        const BOOL runningUnitTests =
        environmentDictionary[@"XCInjectBundleInto"] != nil;

        NSString *delegateName = 
        runningUnitTests ? nil : NSStringFromClass(appDelegateClass);

        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, delegateName);
        }
    }

@"XCInjectBundleInto"in 属性是单元测试包的environmentDictionary路径,由 Xcode 设置。

于 2017-02-06T10:07:40.000 回答
0

我使用 Tomasz Bak 的方法加上一些 dwb 答案的代码并提出以下内容:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

    BOOL runningTests = NSClassFromString(@"XCTestCase") != nil;
    if (runningTests) {
        self.window.rootViewController = [UIViewController new];
        return true;
    }

    // Your normal code below this
    ....
}
于 2016-01-26T10:52:09.223 回答
0

您可以通过在测试目标中将主机应用程序设置为无来做到这一点。

在此处输入图像描述

于 2018-05-23T23:49:07.353 回答