好吧!花了一段時間才弄清楚如何做到這一點。我會全面解釋我的思維過程。抱歉,長期以來。我不得不弄清楚我測試的是什麼。我的代碼有兩件事:它啓動一個重複計時器,然後該計時器有一個回調讓我的代碼執行其他操作。這是兩個單獨的行爲,這意味着兩個不同的單元測試。
那麼如何編寫單元測試來驗證代碼是否正確啓動重複計時器?有三件事情就可以在一個單元測試測試:
- 的方法
- (優選通過一個公共接口提供)系統的狀態或行爲的改變
- 的相互作用的返回值你的代碼有,你不控制
隨着NSTimer
和NSRunLoop
一些其他的代碼,我必須測試的互動,因爲沒有辦法從外部驗證定時器配置正確。嚴重的是,沒有repeats
財產。你必須攔截創建定時器本身的方法調用。
接下來,我意識到,如果我使用+scheduledTimerWithTimeInterval:target:selector:userInfo:repeats
自動啓動計時器,我將不必觸碰NSRunLoop
。這是我不得不測試的一個互動。
最後,爲了創建一個期望+scheduledTimerWithTimeInterval:target:selector:userInfo:repeats
被調用,你必須模擬NSTimer類,幸好OCMock現在可以做到這一點。這裏的測試是什麼樣子:
id mockTimer = [OCMockObject mockForClass:[NSTimer class]];
[[mockTimer expect] scheduledTimerWithTimeInterval:1.0
target:[OCMArg any]
selector:[OCMArg anySelector]
userInfo:[OCMArg any]
repeats:YES];
<your code that should create/schedule NSTimer>
[mockTimer verify];
看着這個測試,我想,「等一下,你怎麼可以實際測試計時器配置了正確的目標和選擇?」那麼,我終於意識到我不應該在乎它是否配置了一個特定的目標和選擇器,我應該只關心當計時器觸發時,它做我所需要的。這對於編寫好的,面向未來的單元測試來說非常重要:確實儘量不要依賴專用接口或實現細節,因爲這些事情會發生變化。相反,測試你的代碼不會改變的行爲,並通過公共接口來完成。
這將我們帶到了第二次單元測試:計時器是否做了我需要它做的事情?爲了測試這個,謝謝NSTimer
有-fire
,這導致定時器在目標上執行選擇器。因此,你甚至都不需要創建一個假的NSTimer
,或者做一個提取&覆蓋創建自定義的模擬定時器,所有你需要做的就是讓它撕裂:
id mockObserver = [OCMockObject observerMock];
[[NSNotificationCenter defaultCenter] addMockObserver:mockObserver
name:@"SomeNotificationName"
object:nil];
[[mockObserver expect] notificationWithName:@"SomeNotificationName"
object:[OCMArg any]];
[myCode startTimer];
[myCode.timer fire];
[mockObserver verify];
[[NSNotificationCenter defaultCenter] removeObserver:mockObserver];
有關此測試的幾點意見:
- 當計時器火災,測試預計在一
NSNotification
發佈到默認NSNotificationCenter
。 OCMock
管理不讓人失望:通知廣播測試是這麼簡單。
- 要實際觸發定時器觸發,您需要對定時器的引用。我的班級在測試中不公開NSTimer的公共接口,所以我這樣做的方式是創建一個類擴展,將私有的NSTimer屬性暴露給我的測試,as described in this SO post。
您不應該驗證它是否真的被調度_運行。調度和運行位不是你的代碼;即使你單元測試了它們並且失敗了,你能做些什麼呢?你只需要確保你的代碼能夠完成它到目前爲止需要做的事情,並與框架正確接口。因此,我認爲第三是答案 - 告訴模擬'NSTimer'類對象期望'timerWithTimeInterval:...' –
模擬'NSRunLoop'也是有意義的; Mike Ash有一篇文章可能會對此有所幫助:https://www.mikeash.com/pyblog/friday-qa-2010-01-01-nsrunloop-internals.html –
Josh,感謝您的指導!我結束了對這個問題的更多思考,並且你對「調度和運行位不是你的代碼」的評論提醒我,我會在某處閱讀,「不要測試Apple的實現」。到現在爲止,它沒有任何意義。我試圖設計我的測試來覆蓋所有可能的場景,但是當你使用別人的代碼來做事情時你不能這麼做,你所能做的就是測試交互發生。 –