2017-08-27 53 views
1

我有一個函數不需要每10秒鐘調用一次。每次調用該函數時,我都會將計時器重置爲10秒。使用XCTest在Xcode中測試定時器

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

謝謝!

+1

如果您希望測試等待某種異步情況,則可以使用'XCTestExpectation'。 – Rob

+0

@Rob在myObject.timer上的代碼效果也是同步的,所以不需要XCTestExpectation,對吧? – lonesomewhistle

回答

0

我最終存儲了原始的Timer的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

首先,我會說,我不知道你的對象是如何工作的,當你沒有任何成員叫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() 
    } 
} 

測試(在評論中說明)

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

這是不是最好的做這件事的方式,但它可以讓你查看或如何流你應該達到這個:)

+0

很酷 - 我的refreshTimer不好 - 剛剛編輯。 – lonesomewhistle

0

如果你想等待定時器啓動,你仍然需要使用期望(或Xcode 9的新異步測試API)。

問題在於你想要測試的是什麼。你可能不想測試計時器開火,而是想測試計時器的處理程序實際上在做什麼。 (想必你必須以執行一些有意義的計時器,所以這就是我們應該測試。)

WWDC 2017年視頻Engineering for Testability提供了一個很好的框架內思考如何設計代碼進行單元測試,這需要:

  • 控制輸入;
  • 對輸出的可見性;和
  • 沒有隱藏狀態。

那麼,您的測試有哪些輸入?而且,更重要的是,輸出是什麼。你想在你的單元測試中測試什麼斷言?

視頻還顯示人們可能如何重構代碼,通過明智地使用來實現這一結構的幾個實際的例子:

  • 協議和參數;和
  • 分離邏輯和效果。

如果不知道計時器實際上在做什麼,很難進一步提出建議。也許你可以編輯你的問題並澄清。

相關問題