1. 什么是Mock
當我們在做單元測試的過程中,為了保持測試又短又快和測試的隔離性,希望盡可能少地去實例化一些具體的組件。在現在面向對象的系統中,被測試的對象很可能會依賴於幾個其他的對象,這時候我們就可以使用Mock去代替實例化這些對象。簡單來說,Mock就是在測試中偽造的具有預定行為的具體對象的替身對象。因為被測試對象無法分辨出具體對象和替身對象的差別,所以可以用替身對象去代替具體對象執行測試。
2. 使用Mock的好處
構造一些使用具體對象難以構造或難以出現的對象。如我們朝服務器(第三方服務器)發送請求,也許100次中只返回一次Error,而當我們要測試返回Error情況下的系統的行為是否符合預期,使用具體對象完成比較困難,這時候就需要構造MockObject。
減少一些耗時的操作,例如我們需要測試訪問數據庫,而訪問這個數據庫開銷巨大的時候,我們可以構造一個“虛擬”的數據庫,讓這個數據庫返回我們期望的特定值即可。
甚至有時候因為需要內網或者屏蔽等原因,無法連接服務器的情況,也可以使用“虛擬”一個網絡連接或服務器,讓它返回我們期望的數據即可。
3. 測試框架簡介
XCTest Or GHUnit
XCTest | GHUnit | |
---|---|---|
簡述 | 蘋果官方提供的測試框架 | 相對熱門的第三方測試框架 |
優點 | 與XCode深度集成,無需安裝,而且可以享受蘋果后續的維護 | 有自己的GUI界面,測試結果直觀 |
缺點 | 測試結果難找且信息冗雜 | 集成度不如XCTest,安裝麻煩,不能單獨運行某個測試 |
XCTest和GHUnit都有各自的優缺點,相對來說GHUnit所提供的便利意義並不大,所以更多的開發者會選擇XCTest。
以下是一些Github上的一些知名的開源庫的測試框架選擇:
Expect Or OCHamcrest
Expecta | OCHamcrest | |
---|---|---|
簡述 | 兩者都是斷言的擴展框架,都依賴於CocoaPods | 起源於Java的Hamcrest,OCHamcrest是Hamcrest的一個Objective -C版本 |
優點 | 斷言不必考慮數據類型,可讀性強,使用方便 | 框架成熟,預定義的斷言更加豐富,可自定義斷言,可擴展性高 |
缺點 | 預定義斷言不夠多,可擴展性不高 |
TDD Or BDD Or Not
TDD 的全稱是Test Driven Development,也就是測試驅動開發。它與軟件傳統的先開發后驗證的模式不同,是先驗證后開發。舉個例子來說,師傅砌磚,在TDD模式下會先拉線后砌磚,這樣砌出來的磚都是整齊的。而一些新來的師傅可能會先砌磚,然后再拉線檢查磚是否整齊,若磚不整齊,再繼續做后續的工作。這樣一聽,感覺TDD是不是很厲害,但實現起來非常困難。
BDD的全稱是Behavior Driven Development,也就是行為驅動開發,通過測試來推動整個開發的進行。BDD的理念是描述行為,所以感覺不是在寫代碼,而是在講故事。因此BDD的測試開發語言都十分接近自然語言,可讀性非常強,讓有眼前一亮。現有的BDD框架大多由三部分構成(Given....When....Then....)組成,下面是一段Objective-C語言的BDD框架Kiwi的一段測試代碼:
這個測試用例就是在說Give a Team, when newly created, it should have a name, and should have 11 players. 翻譯成中文就是一個足球隊成立的時候,它應該有一個隊名和11位球員。基本不用注釋就能很容易的讀懂這個測試做了什么。BDD框架的語法差別不大,十分易讀。目前iOS相關的BDD開源熱門框架有:
框架名稱 | 測試語言 | GitHub Stars | 備注 |
---|---|---|---|
Kiwi | Objective-C | 3223 | Kiwi自帶Mock功能,是基於OCMock實現的,所以不能和OCMock共用 |
Specta | Objective-C | 1692 | Kiwi可以看做是帶有Mock和Expecta功能的Specta |
Quick | Swift(Objective-C) | 4698 | |
Sleipnir | Swift | 795 | |
Cedar | Objective-C | 1079 |
對於BDD框架的選擇,使用或不使用更多開發的項目。BDD測試代碼可讀性高,不過相對於XCTest和OCMock的組合,具有一定的學習成本且文檔不如后者豐富,在封裝過程中也失去了一定的靈活性。
目前使用過的BDD框架Kiwi和Specta都不支持單獨運行某個測試樣例,只能使用Command+U一次運行所有測試樣例,當測試樣例逐漸增多的時候就不得不運行一些不想要做的測試。
4. OCMock簡介
如果你已經理解了什么是Mock,那么OCMock就是Objective-C語言下的一個Mock框架。目前OCMock的版本是OCMock3,其API不太多,使用起來方便,這里簡單介紹一些:
Mock的模式:
模式 | 描述 | API |
---|---|---|
Strict Mode | 嚴格類型的Mock,當Mock對象調用了沒有被Stub的方法會拋出異 常。即嚴格意義上的完全Mock。 |
id strictMock = OCMStrictClassMock([NSUserDefaults class]);
|
Nice Mode | 友好類型的Mock,當Mock對象調用了沒有被Stub的方法不會拋出 異常。目前是默認的Mock類型。 |
id niceMock = OCMClassMock([NSUserDefaults class]);
|
Partial Mode |
部分類型的Mock,當Mock對象調用了沒有被Stub的方法會直接執 行具體對象的方法。 |
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; id partialMock = OCMPartialMock(defs);
|
Expect-Run-Verify 和 Verify after running
大多數的Mock框架都遵循了Expect-Run-Verify的原則,老版本的OCMock就是其中之一,這種方法出現了一些弊端。OCMock3支持了一種新的驗證方法Verify After Running,我們不用在運行結束就立即去驗證結果。只要在運行之后,只要在想要驗證的時候調用OCMVerify即可。
NSInvocation
在OCMock中如果使用了Block綁定參數,那么就需要和NSInvocation打交道,在Objective-C中,NSInvocation有兩個默認的參數(0: self , 1:_cmd),在綁定參數的時候要從2開始算。如:
- (void)downloadWeatherDataForZip:(NSString *)zip callback:(void (^)(NSDictionary *response))callback;
如果要動態綁定Callback,那么它對應的序號應該是3(0: self, 1: _cmd, 2: zip, 3: callback)。
通過[invoke getArgument:&storageVariableName atIndex:3];即可。
5. UI Tests相關
待補
6. Unit Tests相關
處理私有方法和屬性
按照測試的原理,我們不應該去測試私有的方法。但在我們測試的時候可能會調用一個私有方法或者私有屬性,如果不將其改為公有是無法去驗證這些行為的。這種情況,我們可以在測試文件的開頭用一個名為UnitTest的Category來暴露私有方法和屬性。如: