如何在执行任何XCTest之前运行一次性设置代码 [英] How to run one-time setup code before executing any XCTest

查看:164
本文介绍了如何在执行任何XCTest之前运行一次性设置代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  class TestBasketExcercise:XCTestCase {
override func setUp(){
SilentSoundEngine.activate()// SoundEngine是单身
}
//测试
}

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

解决方案

TL; DR:


如上所述



注意你必须使用完全限定的类名(即YourTestTargetsName.TestSetup与TestSetup相比,所以Xcode找到了这个类(谢谢, zneak ...)。



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


I have the following problem. I want to execute a piece of code before all test classes are executed. For instance: I don't want my game to use the SoundEngine singleton during executing, but the SilentSoundEngine. I would like to activate the SilentSoundEngine one time not in all tests. All my tests look like this:

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

-Edit- Most of the answers are directed at providing custom superclass for the TestCase. I am looking for a more general and cleaner way to provide the environment that all tests need to execute. Isn't there a "main" function/ Appdelegate like feature somewhere for tests?

解决方案

TL;DR:
As stated here, you should declare an NSPrincipalClass in your test-targets Info.plist. Execute all the one-time-setup code inside the init of this class, since "XCTest automatically creates a single instance of that class when the test bundle is loaded", thus all your one-time-setup code will be executed once when loading the test-bundle.


A bit more verbose:

To answer the idea in your edit first:

Afaik, there is no main() for the test bundle, since the tests are injected into your running main target, therefore you would have to add the one-time-setup code into the main() of your main target with a compile-time (or at least a runtime) check if the target is used to run tests. Without this check, you'd risk activating the SilentSoundEngine when running the target normally, which I guess is undesirable, since the class name implies that this sound-engine will produce no sound and honestly, who wants that? :)

There is however an AppDelegate-like feature, I will come to that at the end of my answer (if you're impatient, it's under the header "Another (more XCTest-specific) approach").


Now, let's divide this question into two core problems:

  1. How can you ensure that the code you want to execute exactly one time when running the tests is actually being executed exactly one time when running the tests
  2. Where should you execute that code, so it doesn't feel like an ugly hack and so it just works without you having to think of it and remember necessary steps each time you write a new test suite


Regarding point 1:

As @Martin R mentioned correctly in his comments to this answer to your question, overriding +load is not possible anymore as of Swift 1.2 (which is ancient history by now :D), and dispatch_once() isn't available anymore in Swift 3.


One approach

When you try to use dispatch_once anyway, Xcode (>=8) is as always very smart and suggests that you should use lazily initialized globals instead. Of course, the term global tends to have everyone indulge in fear and panic, but you can of course limit their scope by making them private/fileprivate (which does the same for file-level declarations), so you don't pollute your namespace.

Imho, they are actually a pretty nice pattern (still, the dose makes the poison...) that can look like this, for example:

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)
}

This prints:

This will be done one time. It doesn't return a result.
This will be done one time. It returns a result.
0
result
1
result
2
result
3
result
4
result
5
result

Side note: Interestingly enough, the private lets are evaluated before the loop even starts, which you can see because if it were not the case, the 0 would have been the very first print. When you comment the loop out, it will still print the first two lines (i.e. evaluate the lets).
However, I guess that this is playground specific behaviour because as stated here and here, globals are normally initialized the first time they are referenced somewhere, thus they shouldn't be evaluated when you comment out the loop.


Another (more XCTest-specific) approach

(This actually solves both point 1 and 2...)

As the company from Cupertino states here, there is a way to run one-time-pre-testing setup code.

To achieve this, you create a dummy setup-class (maybe call it TestSetup?) and put all the one time setup code into its init:

class TestSetup: NSObject {
    override init() {
        SilentSoundEngine.activate()
    }
}

Note that the class has to inherit from NSObject, since Xcode tries to instantiate the "single instance of that class" by using +new, so if the class is a pure Swift class, this will happen:

*** NSForwarding: warning: object 0x11c2d01e0 of class 'YourTestTargetsName.TestSetup' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector +[YourTestTargetsName.TestSetup new]

Then, you declare this class as the PrincipalClass in your test-bundles Info.plist file:

Note that you have to use the fully qualified class-name (i.e. YourTestTargetsName.TestSetup as compared to just TestSetup), so the class is found by Xcode (Thanks, zneak...).

As stated in the documentation of XCTestObservationCenter, "XCTest automatically creates a single instance of that class when the test bundle is loaded", so all your one-time-setup code will be executed in the init of TestSetup when loading the test-bundle.

这篇关于如何在执行任何XCTest之前运行一次性设置代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆