Testing a Timer in Xcode with XCTest

馋奶兔 提交于 2019-12-14 01:30:04

问题


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.

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?

XCTest:

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

  // Test that my timer has been reset.
}

Thanks!


回答1:


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

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:

  • 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.




回答2:


First, I would say I don't know how your object was working when you don't any member called 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 :)




回答3:


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.

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


来源:https://stackoverflow.com/questions/45906662/testing-a-timer-in-xcode-with-xctest

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!