48

我有以下问题。我想在执行所有测试类之前执行一段代码。例如:我不希望我的游戏在执行过程中使用 SoundEngine 单例,而是使用 SilentSoundEngine。我想激活 SilentSoundEngine 一次而不是在所有测试中。我所有的测试都是这样的:

class TestBasketExcercise : XCTestCase {        
    override func setUp() {
        SilentSoundEngine.activate () // SoundEngine is a singleton
    }
    // The tests 
}

-编辑- 大多数答案都针对为 TestCase 提供自定义超类。我正在寻找一种更通用、更简洁的方法来提供所有测试都需要执行的环境。是否有“主要”功能/ Appdelegate 之类的功能用于测试?

4

4 回答 4

68

编写测试类和方法

您还可以选择为类设置 (+ (void)setUp)和拆卸添加自定义方法(+ (void)tearDown),这些方法在类中的所有测试方法之前和之后运行。

在 Swift 中,这将是class方法:

override class func setUp() {
    super.setUp()
    // Called once before all tests are run
}

override class func tearDown() {
    // Called once after all tests are run
    super.tearDown()
}
于 2015-04-23T12:03:34.320 回答
68

TL;DR:如此
所述,您应该在测试目标 Info.plist 中声明一个 NSPrincipalClass。在此类的 init 中执行所有一次性设置代码,因为“XCTest 在加载测试包时会自动创建该类的单个实例”,因此您所有的一次性设置代码将在加载时执行一次测试包。


更详细一点:

首先在您的编辑中回答这个想法:

Afaik,没有main()测试包,因为测试被注入到您正在运行的主目标中,因此您必须将一次性设置代码添加到main()您的主目标的编译时(或至少一个runtime) 检查目标是否用于运行测试。如果没有这个检查,你可能会SilentSoundEngine在正常运行目标时激活,我猜这是不可取的,因为类名意味着这个声音引擎不会产生声音,老实说,谁想要呢?:)

然而,有一个AppDelegate类似的功能,我将在我的回答结束时谈到它(如果你不耐烦,它在标题“另一种(更具体的 XCTest)方法”下)。


现在,让我们把这个问题分成两个核心问题:

  1. 如何确保在运行测试时要执行一次的代码实际上在运行测试时恰好执行一次
  2. 您应该在哪里执行该代码,这样它就不会感觉像一个丑陋的 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 将是第一次打印。当您将循环注释掉时,它仍会打印前两行(即评估让)。
但是,我猜这是特定于游乐场的行为,因为如herehere所述,全局变量通常在第一次在某处引用时被初始化,因此当您注释掉循环时不应评估它们。


另一种(更特定于 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: 在 Info.plist 中声明 PrincipalClass

请注意,您必须使用完全限定的类名(即 YourTestTargetsName.TestSetup 与仅 TestSetup 相比),因此 Xcode 可以找到该类(谢谢,zneak ...)。

正如 XCTestObservationCenter 的文档中所述,“XCTest 会在加载测试包时自动创建该类的单个实例”,因此在加载测试包时,您所有的一次性设置代码都将在 TestSetup 的 init 中执行。

于 2017-01-27T13:24:24.307 回答
5

如果您只想为setUp类中的所有 UI 测试调用该方法一次,在 Xcode 11 中您可以调用override class func setUp()

这种方法是用于 UI 测试的 Apple 文档的一部分: https ://developer.apple.com/documentation/xctest/xctestcase/understanding_setup_and_teardown_for_test_methods

于 2019-10-17T12:34:56.050 回答
3

如果您为测试用例构建了一个超类,那么您可以在超类中运行通用设置,并在子类中执行您可能需要的任何特定设置。我对 Obj-C 比对 Swift 更熟悉,还没有机会对此进行测试,但这应该很接近了。

// superclass
class SuperClass : XCTestCase {        
    override func setUp() {
        SilentSoundEngine.activate () // SoundEngine is a singleton
    }
}

// subclass
class Subclass : Superclass {
    override func setUp() {
        super.setup()
    }
}
于 2015-04-23T12:07:00.037 回答