TL;DR:
如here 所述,您应该在您的测试目标 Info.plist 中声明一个 NSPrincipalClass。在此类的 init 中执行所有一次性设置代码,因为“XCTest 在加载测试包时会自动创建该类的单个实例”,因此您所有的一次性设置代码将在加载时执行一次测试包。
再详细一点:
首先回答您编辑中的想法:
Afaik,测试包没有main(),因为测试被注入到您正在运行的主目标中,因此您必须将一次性设置代码添加到您的主目标的main() 中编译时(或至少是运行时)检查目标是否用于运行测试。如果没有这个检查,你可能会在正常运行目标时激活SilentSoundEngine,我猜这是不可取的,因为类名意味着这个声音引擎不会产生声音,老实说,谁想要呢? :)
但是有一个类似AppDelegate 的功能,我将在我的回答结束时谈到它(如果你不耐烦,它在标题“另一种(更具体的 XCTest)方法”下)。
现在,让我们把这个问题分成两个核心问题:
- 如何确保在运行测试时要只执行一次的代码实际上在运行测试时只执行一次
-
在哪里你应该在哪里执行该代码,这样它就不会像ugly hack 这样它就可以工作,而无需你考虑它并记住每次编写新测试套件时的必要步骤
关于第1点:
正如@Martin R 在他对this answer 的cmets 中正确提到的那样,从Swift 1.2 开始,覆盖+load 不再可能(这是现在的古代历史:D),而dispatch_once() 不是Swift 3 中不再可用。
一种方法
当您尝试使用dispatch_once 时,Xcode (>=8) 一如既往地非常聪明,并建议您应该使用延迟初始化的全局变量。
当然,global 这个词往往会让每个人都沉浸在恐惧和恐慌中,但你当然可以通过将它们设为 private/fileprivate 来限制它们的范围(这对文件级声明也是如此),这样你就不会污染你的命名空间。
恕我直言,它们实际上是一个非常不错的模式(不过,剂量会产生毒药......)看起来像这样,例如:
private let _doSomethingOneTimeThatDoesNotReturnAResult: Void = {
print("This will be done one time. It doesn't return a result.")
}()
private let _doSomethingOneTimeThatDoesReturnAResult: String = {
print("This will be done one time. It returns a result.")
return "result"
}()
for i in 0...5 {
print(i)
_doSomethingOneTimeThatDoesNotReturnAResult
print(_doSomethingOneTimeThatDoesReturnAResult)
}
打印出来:
这将一次性完成。它不返回结果。
这将完成一次。它返回一个结果。
0
结果
1
结果
2
结果
3
结果
4
结果
5
结果
旁注:
有趣的是,甚至在循环开始之前就对私有 let 进行了评估,您可以看到,因为如果不是这种情况,则 0 将是第一次打印。当您将循环注释掉时,它仍会打印前两行(即评估让)。
但是,我猜这是特定于游乐场的行为,因为正如 here 和 here 所述,全局变量通常在第一次在某处被引用时被初始化,因此当您注释掉循环时不应评估它们。
另一种(更具体的 XCTest)方法
(这实际上解决了第 1 点和第 2 点...)
正如来自 Cupertino 的公司所说 here,有一种方法可以运行一次性预测试设置代码。
为了实现这一点,您创建了一个虚拟的设置类(也许称它为 TestSetup?)并将所有一次性设置代码放入它的 init:
class TestSetup: NSObject {
override init() {
SilentSoundEngine.activate()
}
}
注意该类必须继承自 NSObject,因为 Xcode 尝试使用 +new 实例化“该类的单个实例”,因此如果该类是纯 Swift 类,则此会发生:
*** NSForwarding: warning: object 0x11c2d01e0 of class 'YourTestTargetsName.TestSetup' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector +[YourTestTargetsName.TestSetup new]
然后,在 test-bundles Info.plist 文件中将此类声明为 PrincipalClass:
注意您必须使用完全限定的类名(即 YourTestTargetsName.TestSetup 与 TestSetup 相比),因此 Xcode 可以找到该类(谢谢,zneak... )。
正如 XCTestObservationCenter 的文档中所述,“XCTest 会在加载测试包时自动创建该类的单个实例”,因此您所有的一次性设置代码将在加载测试时在 TestSetup 的 init 中执行-捆绑。