TL;DR:如此处
所述,您应该在测试目标 Info.plist 中声明一个 NSPrincipalClass。在此类的 init 中执行所有一次性设置代码,因为“XCTest 在加载测试包时会自动创建该类的单个实例”,因此您所有的一次性设置代码将在加载时执行一次测试包。
更详细一点:
首先在您的编辑中回答这个想法:
Afaik,没有main()
测试包,因为测试被注入到您正在运行的主目标中,因此您必须将一次性设置代码添加到main()
您的主目标的编译时(或至少一个runtime) 检查目标是否用于运行测试。如果没有这个检查,你可能会SilentSoundEngine
在正常运行目标时激活,我猜这是不可取的,因为类名意味着这个声音引擎不会产生声音,老实说,谁想要呢?:)
然而,有一个AppDelegate
类似的功能,我将在我的回答结束时谈到它(如果你不耐烦,它在标题“另一种(更具体的 XCTest)方法”下)。
现在,让我们把这个问题分成两个核心问题:
- 如何确保在运行测试时要执行一次的代码实际上在运行测试时恰好执行一次
- 您应该在哪里执行该代码,这样它就不会感觉像一个丑陋的 hack,因此它可以在您每次编写新测试套件时无需考虑并记住必要的步骤即可工作
关于第1点:
正如@Martin R 在他对您问题的答案的评论中正确提到的那样,从 Swift 1.2开始,覆盖+load
不再是可能的(这是现在的古老历史:D),并且dispatch_once()
在 Swift 3 中不再可用。
一种方法
当您尝试使用时dispatch_once
,Xcode (>=8) 一如既往地非常聪明,并建议您应该使用延迟初始化的全局变量。当然,这个词global
往往会让每个人都沉浸在恐惧和恐慌之中,但您当然可以通过将它们设为私有/文件私有(这对文件级声明也是如此)来限制它们的范围,这样就不会污染您的命名空间。
恕我直言,它们实际上是一个非常好的模式(仍然,剂量会产生毒药......),看起来像这样,例如:
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 点......)
正如来自库比蒂诺的公司所说,有一种方法可以运行一次性预测试设置代码。
为了实现这一点,你创建了一个虚拟的设置类(也许称它为 TestSetup?)并将所有一次性设置代码放入它的 init 中:
class TestSetup: NSObject {
override init() {
SilentSoundEngine.activate()
}
}
请注意,该类必须从 NSObject 继承,因为 Xcode 尝试通过 using 实例化“该类的单个实例” +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 中执行。