如何在执行任何XCTest之前运行一次性设置代码

Ray*_*ond 35 ios xctest swift xctestcase

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

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

-Edit-大多数答案都针对为TestCase提供自定义超类.我正在寻找一种更通用,更清晰的方式来提供所有测试都需要执行的环境.是不是有一个"主要"功能/ Appdelegate喜欢功能的地方进行测试?

Mar*_*n R 56

编写测试类和方法:

您也可以选择为类设置(+ (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()
}
Run Code Online (Sandbox Code Playgroud)

  • @DavidLord:*instance*方法setUp/tearDown为每个测试运行一次.*class*方法setUp/tearDown在所有测试之前/之后运行一次.换句话说,无论有多少测试,类方法都会运行一次. (10认同)
  • 愚蠢的问题:类方法中需要那些`super`调用吗?是否应该在实例化对象上调用`super`,而不是类方法?我当然可以看到,即使使用类方法,类也被放入内存中,因为测试类继承自`XCTestCase`,`super`调用需要存在,但对于一个正在恢复的Java程序员来说,这看起来很奇怪. (3认同)
  • 如果我需要在`class setUp`中使用`expectation`执行异步请求,我该怎么办? (2认同)

Jan*_*ash 47

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


更冗长一点:

要先在编辑中回答这个想法:

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

然而,有一个AppDelegate类似的功能,我将在我的答案结束时(如果你不耐烦,它在标题"另一个(更多XCTest特定的)方法"下).


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

  1. 如何确保在运行测试时要执行一次的代码实际上在运行测试时只执行一次
  2. 你应该在哪里执行该代码,所以它不会像一个丑陋的黑客,所以它只是工作而你不必考虑它并记住每次你编写一个新的测试套件时必要的步骤

关于第1点:

正如@Martin R在他对你的问题的答案的评论中正确提到的+load那样,从Swift 1.2(现在是古代历史:D)开始就不可能超越,并且dispatch_once()在Swift 3中不再可用.


一种方法

当你尝试使用时dispatch_once,Xcode(> = 8)总是非常聪明,并建议你应该使用延迟初始化的全局变量.当然,这个术语global往往让每个人都沉迷于恐惧和恐慌,但你当然可以通过将它们设为private/fileprivate来限制它们的范围(对于文件级声明也是如此),因此你不会污染你的命名空间.

Imho,它们实际上是一个非常漂亮的模式(仍然,剂量使毒药......)看起来像这样,例如:

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)
}
Run Code Online (Sandbox Code Playgroud)

这打印:

这将完成一次.它不会返回结果.
这将完成一次.它返回一个结果.
0
结果
1
结果
2
结果
3
结果
4
结果
5
结果

旁注:有趣的是,私有let在循环开始之前进行评估,你可以看到,因为如果不是这样的话,那么0就是第一次打印.当您对循环进行注释时,它仍将打印前两行(即评估let).
但是,我猜这是游乐场特定的行为,因为如此此处所述,全局变量通常在第一次被引用时被初始化,因此当您注释掉循环时不应该对它们进行求值.


另一种(更具XCTest特定的)方法

(这实际上解决了第1点和第2点...)

由于公司来自Cupertino指出这里,有跑一次性预测试设置代码的方法.

要实现这一点,您需要创建一个虚拟安装类(可能称之为TestSetup?)并将所有一次性安装代码放入其init:

class TestSetup: NSObject {
    override init() {
        SilentSoundEngine.activate()
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,该类必须继承自NSObject,因为Xcode尝试通过使用实例化"该类的单个实例" +new,因此如果该类是纯Swift类,则会发生这种情况:

*** NSForwarding: warning: object 0x11c2d01e0 of class 'YourTestTargetsName.TestSetup' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector +[YourTestTargetsName.TestSetup new]
Run Code Online (Sandbox Code Playgroud)

然后,将此类声明为test-bundles Info.plist文件中的PrincipalClass: 在Info.plist中声明PrincipalClass

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

如XCTestObservationCenter文档中所述,"XCTest在加载测试包时会自动创建该类的单个实例",因此在加载测试包时,所有一次性安装代码都将在TestSetup的init中执行.

  • 每次我在答案XD中写测试时,每个人都喝一个 (8认同)
  • 感谢您的详细答复。仅供参考-我发现在Xcode 8.3.3中,使用NSPricipalClass DID的完全限定名称(例如YourTestTargetsName.TestSetup)不起作用,但是简单名称确实起作用(例如TestSetup)。 (2认同)
  • 详细信息在 7:30 左右。关于测试目标名称的一个重要警告:你的测试目标名称似乎必须是一个有效的标识符,以便加载器找到主体类,即使 Xcode 通常不需要——例如不是`FooTests macOS`但是`FooTestsMacOS`。否则,您将获得 **Info.plist 为 NSPrincipalClass 指定的 <name>,但找不到与该名称匹配的类**。可能有一些改名技巧可以解决这个问题,但我不知道它是什么。 (2认同)

Met*_*kin 6

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

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