Swift 使用XCTest在Xcode中测试计时器

Swift 使用XCTest在Xcode中测试计时器,swift,xcode,timer,xctest,Swift,Xcode,Timer,Xctest,我有一个函数,它不需要每隔10秒调用一次。每次调用该函数时,我都会将计时器重置为10秒 class MyClass { var timer:Timer? func resetTimer() { self.timer?.invalidate() self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) { (timer) -> Void in self.

我有一个函数,它不需要每隔10秒调用一次。每次调用该函数时,我都会将计时器重置为10秒

class MyClass {
  var timer:Timer?

  func resetTimer() {
    self.timer?.invalidate()
    self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) {
      (timer) -> Void in
      self.performAction()        
    }
  }

  func performAction() {
    // perform action, then
    self.resetTimer()
  }
}
我想测试调用performAction()手动将计时器重置为10秒,但似乎找不到任何好方法。Stubbing resetTimer()感觉测试并没有告诉我足够的功能。我错过什么了吗

XCTest:

func testTimerResets() {
  let myObject = MyClass()
  myObject.resetTimer()
  myObject.performAction()

  // Test that my timer has been reset.
}

谢谢

首先,我想说,当您没有任何名为
refreshTimer
的成员时,我不知道您的对象是如何工作的

class MyClass {
    private var timer:Timer?
    public var  starting:Int = -1 // to keep track of starting time of execution
    public var  ending:Int   = -1 // to keep track of ending time 


    init() {}

    func invoke() {
       // timer would be executed every 10s 
        timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(performAction), userInfo: nil, repeats: true)
        starting = getSeconds()
        print("time init:: \(starting) second")

    }

    @objc func performAction() {
        print("performing action ... ")
        /*
         say that the starting time was 55s, after 10s, we would get 05 seconds, which is correct. However for testing purpose if we get a number from 1 to 9 we'll add 60s. This analogy works because ending depends on starting time  
        */
        ending = (1...9).contains(getSeconds()) ? getSeconds() + 60 : getSeconds()
        print("time end:: \(ending) seconds")
        resetTimer()
    }

    private func resetTimer() {
        print("timer is been reseted")
        timer?.invalidate()
        invoke()
    }

    private func getSeconds()-> Int {
        let seconds = Calendar.current.component(.second, from: Date())
        return seconds 
    }

    public func fullStop() {
        print("Full Stop here")
        timer?.invalidate()
    }
}
测试(注释中的解释)


这不是最好的方法,但是它为您提供了如何实现这一点的视图或流程:)

如果您想等待计时器启动,您仍然需要使用预期(或Xcode 9新的异步测试API)

问题是你到底想测试什么。您可能不想只测试计时器是否触发,而是想测试计时器的处理程序实际在做什么。(假设您有一个计时器来执行一些有意义的操作,所以这就是我们应该测试的。)

WWDC 2017视频为思考如何为单元测试设计代码提供了一个很好的框架,需要:

  • 控制输入
  • 对产出的可见性;及
  • 没有隐藏状态
那么,测试的输入是什么?更重要的是,输出是什么。您希望在单元测试中测试哪些断言

该视频还展示了一些实际示例,说明如何通过明智地使用以下工具重构代码以实现此结构:

  • 协议和参数化;及
  • 分离逻辑和效果

在不知道计时器实际在做什么的情况下,很难给出进一步的建议。也许您可以编辑您的问题并澄清。

我最终存储了原始计时器的fireDate,然后检查在执行操作后,新的fireDate是否设置为比原始fireDate更晚的值

func testTimerResets() {
  let myObject = MyClass()
  myObject.resetTimer()
  let oldFireDate = myObject.timer!.fireDate
  myObject.performAction()

  // If timer did not reset, these will be equal
  XCTAssertGreaterThan(myObject.timer!.fireDate, oldFireDate)
}

很好,你找到了一个解决方案,但回答了标题中的问题

要测试计时器是否实际工作(即运行并调用回调),我们可以执行以下操作:

导入XCTest
@可测试导入MyApp
类MyClassTest:XCTestCase{
func testCallback\u triggerscaledontime()抛出{
let exp=期望值(说明:“等待计时器完成”)
//笨蛋。
让实例:MyClass!=MyClass()
instance.delay=2000;//毫秒等于2秒。
instance.callback={uu}in
exp.fulfill();
}
//实际测试。
instance.startTimer();
//暂停至完成(最多睡眠5秒,
//else将在调用“exp.fulfill()”后立即恢复)。
如果XCTWaiter.wait(for:[exp],超时:5.0)=.timedOut{
XCTFail(“计时器未及时完成!”)
}
}
}
当有一门课时,比如:

public class MyClass {
    var delay: Int = 0;
    var callback: ((timer: Timer) -> Void)?
    
    func startTimer() {
        let myTimer = Timer(timeInterval: Double(self.delay) / 1000.0, repeats: false) {
            [weak self] timer in
            guard let that = self else {
                return
            }
            that.callback?(timer)
        }
        RunLoop.main.add(myTimer, forMode: .common)
    }
}

如果希望测试等待某个异步条件,可以使用
xtestexpection
public class MyClass {
    var delay: Int = 0;
    var callback: ((timer: Timer) -> Void)?
    
    func startTimer() {
        let myTimer = Timer(timeInterval: Double(self.delay) / 1000.0, repeats: false) {
            [weak self] timer in
            guard let that = self else {
                return
            }
            that.callback?(timer)
        }
        RunLoop.main.add(myTimer, forMode: .common)
    }
}