用XCTest测试Xcode中的计时器

时间:2023-01-16 19:33:16

I have a function that does not need to be called any more than every 10 secs. Every time I invoke the function, I reset the timer to 10 secs.

我有一个函数,它不需要被称为每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()
  }
}

I would like to test that calling performAction() manually resets the timer to 10 secs, but I can't seem to find any good way to do it. Stubbing resetTimer() feels like the test wouldn't really be telling me enough about the functionality. Am I missing something?

我想要测试调用performAction()手动将计时器重置为10秒,但是我似乎找不到任何好的方法。Stubbing resetTimer()感觉这个测试并没有告诉我足够的功能。我遗漏了什么东西?

XCTest:

XCTest:

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

  // Test that my timer has been reset.
}

Thanks!

谢谢!

3 个解决方案

#1


1  

First, I would say I don't know how your object was working when you don't any member called refreshTimer.

首先,我想说我不知道你的对象是如何工作的,当你没有任何成员叫做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()
    }
}

Testing (explanation in the comments)

测试(注释中解释)

let testObj = MyClass()
    // at init both starting && ending should be -1
    XCTAssertEqual(testObj.starting, -1)
    XCTAssertEqual(testObj.ending, -1)

    testObj.invoke()
    // after invoking, the first member to be changed is starting
    let startTime = testObj.starting
    XCTAssertNotEqual(startTime, -1)
    /*
    - at first run, ending is still -1 
    - let's for wait 10 seconds 
    - you should use async  method, XCTWaiter and expectation here 
    - this is just to give you a perspective or way of structuring your solution
   */
    DispatchQueue.main.asyncAfter(deadline: .now() + 10 ) {
        let startTimeCopy = startTime
        let endingTime = testObj.ending
        XCTAssertNotEqual(endingTime, -1)
        // take the difference between start and end
        let diff = endingTime - startTime
        print("diff \(diff)")
        // no matter the time, diff should be 10
        XCTAssertEqual(diff, 10)

        testObj.fullStop()
    }

this is not the best of way of doing it, however it gives you view or a flow on how you should achieve this :)

这并不是最好的方法,但它给了你如何实现这一点的观点或流程:

#2


0  

If you want to wait for the timer to fire, you'll still need to use expectations (or Xcode 9's new asynchronous testing API).

如果您希望等待计时器启动,您仍然需要使用expect(或者Xcode 9的新异步测试API)。

The question is what precisely you're trying to test. You presumably don't want to just test that the timer fired, but rather you want to test what the timer's handler is actually doing. (Presumably you have a timer in order to perform something meaningful, so that's what we should be testing.)

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

WWDC 2017 video Engineering for Testability offers a nice framework to be thinking about how to design code for unit tests , which need:

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

  • control over inputs;
  • 控制输入;
  • visibility to outputs; and
  • 可见性输出;和
  • no hidden state.
  • 没有隐藏的状态。

So, what are the inputs to your test? And, more importantly, what is the output. What assertions do you want to test for in your unit test?

那么,测试的输入是什么呢?更重要的是,输出是什么。您希望在单元测试中测试什么断言?

The video also shows a few practical examples of how one might refactor code to achieve this structure through judicious use of:

该视频还展示了一些实际的例子,说明如何重构代码,通过明智地使用:

  • protocols and parameterization; and
  • 协议和参数化;和
  • separating logic and effects.
  • 分离逻辑和效果。

It's hard to advise further without knowing what the timer is actually doing. Perhaps you can edit your question and clarify.

如果不知道计时器实际上在做什么,就很难给出进一步的建议。也许你可以修改你的问题并澄清。

#3


0  

I ended up storing the original Timer's fireDate, then checking to see that after the action was performed the new fireDate was set to something later than the original fireDate.

最后,我存储了原始计时器的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)
}

#1


1  

First, I would say I don't know how your object was working when you don't any member called refreshTimer.

首先,我想说我不知道你的对象是如何工作的,当你没有任何成员叫做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()
    }
}

Testing (explanation in the comments)

测试(注释中解释)

let testObj = MyClass()
    // at init both starting && ending should be -1
    XCTAssertEqual(testObj.starting, -1)
    XCTAssertEqual(testObj.ending, -1)

    testObj.invoke()
    // after invoking, the first member to be changed is starting
    let startTime = testObj.starting
    XCTAssertNotEqual(startTime, -1)
    /*
    - at first run, ending is still -1 
    - let's for wait 10 seconds 
    - you should use async  method, XCTWaiter and expectation here 
    - this is just to give you a perspective or way of structuring your solution
   */
    DispatchQueue.main.asyncAfter(deadline: .now() + 10 ) {
        let startTimeCopy = startTime
        let endingTime = testObj.ending
        XCTAssertNotEqual(endingTime, -1)
        // take the difference between start and end
        let diff = endingTime - startTime
        print("diff \(diff)")
        // no matter the time, diff should be 10
        XCTAssertEqual(diff, 10)

        testObj.fullStop()
    }

this is not the best of way of doing it, however it gives you view or a flow on how you should achieve this :)

这并不是最好的方法,但它给了你如何实现这一点的观点或流程:

#2


0  

If you want to wait for the timer to fire, you'll still need to use expectations (or Xcode 9's new asynchronous testing API).

如果您希望等待计时器启动,您仍然需要使用expect(或者Xcode 9的新异步测试API)。

The question is what precisely you're trying to test. You presumably don't want to just test that the timer fired, but rather you want to test what the timer's handler is actually doing. (Presumably you have a timer in order to perform something meaningful, so that's what we should be testing.)

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

WWDC 2017 video Engineering for Testability offers a nice framework to be thinking about how to design code for unit tests , which need:

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

  • control over inputs;
  • 控制输入;
  • visibility to outputs; and
  • 可见性输出;和
  • no hidden state.
  • 没有隐藏的状态。

So, what are the inputs to your test? And, more importantly, what is the output. What assertions do you want to test for in your unit test?

那么,测试的输入是什么呢?更重要的是,输出是什么。您希望在单元测试中测试什么断言?

The video also shows a few practical examples of how one might refactor code to achieve this structure through judicious use of:

该视频还展示了一些实际的例子,说明如何重构代码,通过明智地使用:

  • protocols and parameterization; and
  • 协议和参数化;和
  • separating logic and effects.
  • 分离逻辑和效果。

It's hard to advise further without knowing what the timer is actually doing. Perhaps you can edit your question and clarify.

如果不知道计时器实际上在做什么,就很难给出进一步的建议。也许你可以修改你的问题并澄清。

#3


0  

I ended up storing the original Timer's fireDate, then checking to see that after the action was performed the new fireDate was set to something later than the original fireDate.

最后,我存储了原始计时器的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)
}