【问题标题】:Unit Testing in Xcode, does it run the app?Xcode中的单元测试,它运行应用程序吗?
【发布时间】:2013-03-20 19:56:03
【问题描述】:

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

当您使用 cmd+U 来运行单元测试(例如 OCUnit)时,它是否真的调用了 main.m、新的 appDelegate 并像您按了 cmd+R 一样运行应用程序?

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

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

【问题讨论】:

    标签: iphone ios xcode unit-testing


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

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

          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);
              }
          }
      

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

      【讨论】:

        【解决方案3】:

        使用 xCode 7 和 xCtool

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

        为了让它工作,

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

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

        2。如果没有,请安装 xctool。

        brew install xctool
        

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

        xctool -workspace yourWorkspace.xcworkspace -scheme yourScheme run-tests -sdk iphonesimulator
        

        【讨论】:

        • 但是,测试不会在 iOS 应用程序的上下文中运行,而且很多事情都无法正常工作。例如钥匙串和核心数据。如果你有 UI 测试,这根本行不通。不过它适用于单元测试。
        【解决方案4】:

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

        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

        【讨论】:

          【解决方案5】:

          我使用 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
              ....
          }
          

          【讨论】:

            【解决方案6】:

            在 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 中的代码将删除从情节提要创建的所有视图。

            【讨论】:

            • 这对我来说非常有效,而且它似乎是最简单的解决方案!!谢谢
            【解决方案7】:

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

            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;
            }
            

            【讨论】:

            • 非常好,我从没想过要替换一个不同的应用程序委托!我会将测试简化为 BOOL runningTests = NSClassFromString(@"SenTestCase") != nil;
            • @JonReid 不错的补充。我从来没有想过用这种方式简化它!
            • 对于 XCODE 5 - BOOL inTests = (NSClassFromString(@"XCTest") != nil);
            • @CarlJ 这可能是您的项目设置中的问题。请注意,不应将 OCTest/XCTest 框架链接到您的主要目标中。确保它仅在您的测试目标中。
            • 如果我有逻辑测试、应用程序测试和普通应用程序 AppDelegates 想要在它们之间切换怎么办?如何在应用程序和逻辑测试之间切换?
            【解决方案8】:

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

            1:删除AppDelegate.swift中的@UIApplicationMain

            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))
            }
            

            【讨论】:

            • XCode 7 (beta 5) + iOS9 - 使用 Process.argc, Process.unsafeArgv 而不是 C_ARGC, C_ARGV
            • 使用这种方法,测试 AppDelegate 是否仍会导致情节提要加载?如果是这样,可以避免这种情况吗?
            • 完全体面 - 是的,它仍然可以加载。有一种方法可以解决这个问题,但我想找到一个更好的方法。复制正在测试的目标,我们称之为 myApp 和复制 myApp 副本。取消选中 myApp 复制目标的 Main.storyboard 的成员资格。添加一个名为 Main.storyboard 的新故事板,并使其成为 myApp 复制目标的成员。更改 myApp 测试目标,使其测试 myApp 副本而不是 myApp。现在将使用空的 Main.storyboard,并且不会实例化视图控制器。不过我想找到一种更简单的方法。
            【解决方案9】:

            这是 Sulthan 答案的变体,它使用 XCTest,这是 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
            

            【讨论】:

              【解决方案10】:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2013-12-11
                • 1970-01-01
                • 2013-09-27
                • 2014-07-08
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多