【问题标题】:How to run one-time setup code before executing any XCTest如何在执行任何 XCTest 之前运行一次性设置代码
【发布时间】:2015-07-01 13:55:39
【问题描述】:

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

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

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

【问题讨论】:

  • 我参加聚会有点晚了,但是在你的 setUp() 函数中放置一个 dispatch_once() 怎么样?

标签: ios swift xctest xctestcase


【解决方案1】:

来自Writing Test Classes and Methods

您可以选择为类设置添加自定义方法 (+ (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()
}

【讨论】:

  • @DavidLord:instance 方法 setUp/tearDown 对每个测试运行一次。 class 方法 setUp/tearDown 在所有测试之前/之后运行一次。换句话说,无论有多少测试,类方法都会运行一次。
  • 哦!现在在其他地方看到了这个 - 是的,你是对的。想要编辑答案以包含 override funcoverride class func 的示例?
  • 愚蠢的问题:类方法中是否需要那些super 调用? super 是否应该只在实例化对象上调用,而不是类方法?我当然可以看到这样的说法,即使使用类方法,该类也被放入内存中,并且由于测试类继承自 XCTestCase ,所以 super 调用需要在那里,但是对于正在恢复的 Java 程序员来说,这看起来很奇怪.
  • 如果我需要在class setUp 中使用expectation 进行异步请求,我该怎么办?
  • 此外,此答案的代码块中的 setUp() 和 tearDown() 方法中的 cmets 有点误导,因为 class func setUp()tearDown() 执行一次 对于每个测试套件。如果 OP 只有一个,他们将实现所需的行为,但是一旦添加第二个,他们就必须将设置提取到超类中,以确保在运行任何测试之前调用它。这也会导致设置被执行两次(每个子类测试套件一次)。
【解决方案2】:

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


再详细一点:

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

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

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


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

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


另一种(更具体的 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 中执行-捆绑。

【讨论】:

  • 我每次在答案中写测试,每个人都会喝一杯XD
  • 我不明白反对意见。答案对某人不起作用还是有什么我应该改进或更正的? ://
  • 感谢您如此详细的回复。仅供参考 - 我发现在 Xcode 8.3.3 中,对 NSPricipalClass 使用完全限定名称(例如 YourTestTargetsName.TestSetup)不起作用,但简单名称确实起作用(例如 TestSetup)。
  • 有关设置您自己的测试观察者的良好介绍,请查看此 WWDC 视频——跳至 5 分钟标记以获取与此主题相关的具体信息:developer.apple.com/videos/play/wwdc2016/409 XCTestObservation 上的 API 条目还概述了您可以在课堂上观察到的事件:developer.apple.com/documentation/xctest/xctestobservation
  • 详情在7:30左右。关于测试目标名称的一个重要警告:您的测试目标名称似乎必须是一个有效的标识符才能让加载程序找到主体类,即使 Xcode 通常不需要 - 例如。不是FooTests macOS,而是FooTestsMacOS。否则,您将获得 Info.plist 为 NSPrincipalClass 指定 ,但找不到与该名称匹配的类。可能有一些名称修改技巧可以解决这个问题,但我不知道它是什么。
【解决方案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()
    }
}

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-01
      • 2018-03-08
      • 2018-06-15
      • 1970-01-01
      • 2021-08-06
      相关资源
      最近更新 更多