iOS Swift:为XCTest分离AppDelegate

17
由于一些问题,我希望在项目中的 XCTest 目标运行一个单独的应用程序委托。使用 ObjC,这个过程相对简单:操作 main.m 文件(请参见:https://dev59.com/uGUo5IYBdhLWcg3w3ycF#15725328)。
由于似乎 Swift 应用程序是使用 @UIApplicationMain 在 AppDelegate 中初始化的,所以是否可能为测试目标初始化一个单独的 AppDelegate?
3个回答

18

强烈不建议在正常代码中添加条件来检查它是否正在被测试。相反,在测试中应该模拟您的AppDelegate来执行任何您想要的操作。

然后,你可以在每个XCTestCase的父类的setUp中替换UIApplication的委托。

class MockAppDelegate:NSObject, UIApplicationDelegate {

}


class BaseTest: XCTestCase {

    override func setUp() {
        super.setUp()
        UIApplication.shared.delegate = MockAppDelegate()
       }
}
class Test1: BaseTest {

    override func setUp() {
        super.setUp()
        // normal testing
       }
}
如果您仍想在测试中停止代码执行,这是我的方法,可以很好地运行:
您可以向应用程序添加启动参数,指示这是测试运行。 App Start execution 这些参数可以从NSUserDefaults中访问。
#define IS_TESTS [[NSUserDefaults standardUserDefaults] boolForKey:@"TESTING"]

4
这是一个很好的观点,我同意不要将与测试相关的条件添加到正常的代码中。实际上,我想做的是防止代码在主AppDelegate(处理用户和会话状态以及数据存储)中执行。不幸的是,创建一个MockAppDelegate并不能实现这一点。 - Whoa
7
这不会阻止普通的应用程序委托运行,对吧?我的意思是,通常情况下,测试套件开始运行之前会触发AppDelegate的didFinishLaunchingWithOptions - Marc-Alexandre Bérubé
当您运行测试时,您会在启动时传递参数,这些参数可以从NSUserDefaults中访问。因此回答您的问题:由于在测试套件开始运行之前已知该参数,因此它将防止AppDelegate运行。 - Michał Hernas
2
@MichałHernas @Marc-AlexandreBérubé 我正在尝试这个,它运行正常的应用程序委托 - setUp() 在正常的应用程序委托运行didFinishLaunchingWithOptions之后执行。我的测试委托从未被使用。 - User
代理是一个未持有的引用,因此这个赋值是不安全的,可能会导致晦涩的崩溃。将MockAppDelegate()变成一个成员、全局变量或以其他方式创建一个强引用。 - MattD
显示剩余3条评论

9
为了在Swift中实现这一点,您需要执行以下几个步骤:
  1. If you are using Storyboards, create your view stack programmatically on your AppDelegate.

    Remove Main.storyboard from your project configuration Project configuration

    Delete @UIApplicationMain from the beginning of your AppDelegate and add this code.

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
       let storyboard = UIStoryboard(name: "Main", bundle: nil)
       let vc = storyboard.instantiateInitialViewController()
    
       let window = UIWindow(frame: UIScreen.main.bounds)
       window.rootViewController = vc
    
       window.makeKeyAndVisible()
       self.window = window
    
       return true
    }
    
  2. Create a new file at the root your target and call it main.swift.

    Add this code if you don't need to do any setup for your tests

    import UIKit
    
    let kIsRunningTests = NSClassFromString("XCTestCase") != nil
    let kAppDelegateClass = kIsRunningTests ? nil : NSStringFromClass(AppDelegate.self)
    
    UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, kAppDelegateClass)
    

    If you need to make some configuration before you run the tests, create a new class FakeAppDelegate as a subclass from NSObject and add your setup code there.

    Put this code in main.swift

    import UIKit
    
    let kIsRunningTests = NSClassFromString("XCTestCase") != nil
    let kAppDelegateClass = kIsRunningTests ? NSStringFromClass(FakeAppDelegate.self) : NSStringFromClass(AppDelegate.self)
    
    UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, kAppDelegateClass)
    

兄弟,它成功了!!!终于我可以运行我的单元测试而不必运行整个应用程序。如果你有任何关于这种方法的更新,我很想看看。 - 23inhouse

2
以下是解决方案:
  1. 复制您现有应用程序的目标,并将其重命名为适当的名称。在您的情况下,可能是“TestingHarness”或类似的名称。请注意,您还需要更改捆绑标识符并重命名相应的Info.plist文件。重命名Info.plist文件意味着您需要在新目标的Build Settings选项卡中更改Info.plist文件名设置以匹配新名称。

  2. 创建另一个*AppDelegate.swift文件。在您的情况下,我会称之为TestAppDelegate.swift

  3. 将现有的AppDelegate.swift文件内容复制到TestAppDelegate.swift中,并根据需要进行编辑。确保保留@UIApplicationMain注释并实现所需的UIApplicationDelegate回调。

  4. 更改每个*AppDelegate.swift文件的目标成员资格,以使AppDelegate.swift不包含在新的“TestHarness”目标中,而TestAppDelegate.swift不包含在主应用程序的目标中。(您可以通过在文件浏览器中选择它并打开默认情况下可以访问的右侧边栏中的文件检查器,或通过在菜单下选择它来编辑文件的目标成员资格 -> 实用程序。)

  5. 现在,您有两个具有单独的应用程序委托的单独目标,可以独立构建和运行。最后一步是将您的新“TestHarness”目标选择为测试目标的主机应用程序。(单击文件浏览器中的顶级项目条目,然后单击子列表中的所需测试目标。在常规选项卡上,您将看到Host Application作为唯一可用的下拉菜单。)

注意:这些说明适用于Xcode 7.2。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接