Mock介紹
什么是mock測試?
對于一些不容易構造或不容易獲取的對象,此時你可以創建一個虛擬的對象(mock object)來完成測試。
例如你可能要嘗試100次才會返回一個NSError,通過mock object你可以自行創建一個NSError對象,測試在出錯情況下程序的處理是否符合你的預期。
例如你要連接服務器但是服務器在實驗室,你在外工作的時候就無法測試了(小弟就試過這種情況,非常反感),這個時候你可以創建一個虛擬的服務器,并返回一些你指定的數據,從而繞過服務器。
例如假設你要訪問一個數據庫,但是訪問過程的開銷巨大,這時你可以虛擬一個數據庫,并且返回一些自行定制的數據,從而繞過了數據庫的訪問。
mock的思想很簡單:沒有條件?我們就自行創造條件。
OCMock介紹
OCMock是一個用于為iOS或Mac OS X項目配置Mock測試的開源項目。
其實現思想就是根據要mock的對象的class來創建一個對應的對象,并且設置好該對象的屬性和調用預定方法后的動作(例如返回一個值,調用代碼塊,發送消息等等),然后將其記錄到一個數組中,接下來開發者主動調用該方法,最后做一個verify(驗證),從而判斷該方法是否被調用,或者調用過程中是否拋出異常等。
其實就是可以把它當做我們偽造的一個對象,我們給它一些預設的值之類的,然后就可以進行對應的驗證了。
配置
到OCMock的官網下載dmg文件,打開后里面有個iOS library
文件夾。把iOS library
里的文件加入到你的項目里,按這篇教程來進行配置。
OCMock文檔,可以到這里查看如何詳細使用OCMock。
例子中使用到的方法說明
(以下數字為OCMock文檔中的數字目錄)
1.1. Class mocks
id classMock = OCMClassMock([SomeClass class]);
創建mock object當做類的實例。
2.1. Stubbing methods that return objects
OCMStub([mock someMethod]).andReturn(anObject);
告訴mock object當它調用someMethod的時候應該返回anObject。
3.1. Verify-after-running
id mock = OCMClassMock([SomeClass class]);
/* run code under test */
OCMVerify([mock someMethod]);
/* run code under test */寫一些對應的代碼,然后 OCMVerify([mock someMethod]);
驗證方法是否被調用。
3.2. Stubs and verification
id mock = OCMClassMock([SomeClass class]);
OCMStub([mock someMethod]).andReturn(myValue);
/* run code under test */
OCMVerify([mock someMethod]);
4.1. The any constraint
OCMStub([mock someMethodWithAnArgument:[OCMArg any]])
OCMStub([mock someMethodWithPointerArgument:[OCMArg anyPointer]])
OCMStub([mock someMethodWithSelectorArgument:[OCMArg anySelector]])
實際例子
以下的例子Demo:DSOCMockDemo
例子1
使用了文檔中的OCMock文檔中以下幾種方法:
1.1 Class mocks
2.1 Stubbing methods that return objects
3.1 Verify-after-running
3.2 Stubs and verification
4.1 The any constraint
這是一個來自于OCMock上的例子。我這里就做一下翻譯和Demo。
為了使我們更加具體的理解使用OCMock,這里假設我們寫了一個接收來自于Twitter信息的應用。
有一個TwitterViewController類,一個TwitterConnection用來調用Twitter API得到數據,和一個TweetView類用來顯示tweet對象。
下面是TwitterViewController
類,有connection
和tweetView
對象。
@interface TwitterViewController : UIViewController
@property(nonatomic, strong)TwitterConnection *connection;
@property(nonatomic, strong)TweetView *tweetView;
- (void)updateTweetView;
@end
TwitterConnection是一個網絡連接類,有一個fetchTweets方法用來接收請求回來的信息。返回一個Tweet對象的數組。
@interface TwitterConnection : NSObject
- (NSArray *)fetchTweets;
@end
TweetView
有一個addTweet:
方法用來添加每一個Tweet對象到頁面上。
@interface TweetView : UIView
- (void)addTweet:(Tweet *)aTweet;
@end
為什么使用mocks用來測試
當我們為updateTweetView來寫一個測試類的時候我們要考慮它有哪些相對應的依賴,也就是TwitterConnection和TweetView。在這個例子里我們需要實例化一個真正的TwitterConnection對象來請求真實數據然后使用它。這樣的話會有幾個問題:
- 使用真實的connection會使測試變慢,因為它還要去請求網絡。
- 我們永遠不知道每一次Twitter返回的數據是什么。
- 很難測試錯誤的返回,因為Twitter一般不會返回錯誤。
解決的方法就是偽造一個假的connection,既一個stub。
下圖就是使用測試的代碼:
例子2
OCMock會在mock實例上沒有找到相同名字的實例方法的時候去找同名的類方法。
使用例子一種的類,給TwitterConnection
加個類方法
+ (NSArray *)fetchTweets2;
。
我們可以看到例子2和例子中classMethod和instanceMethod的stub方式一樣。
例子3
使用了文檔中的OCMock文檔中以下幾種方法:
1.3 Strict class and protocol mocks
7.1 Expect-run-verify
7.2 Strict mocks and failing fast
7.3 Stub actions and expect
當我們使用普通的mock的時候是這樣的:
- (void)testStrictMock3{
id classMock = OCMClassMock([TweetView class]);
//設置期望或預設,這個classMock需要執行addTweet方法且參數不為nil。 不然的話會拋出異常
//OCMExpect([classMock addTweet:[OCMArg isNotNil]]);
//OCMStub([classMock addTweet:[OCMArg isNotNil]]);
/* 如果不執行以下代碼的話會拋出異常 */
Tweet *testTweet = [[Tweet alloc] init];
testTweet.userName = @"齊滇大圣";
[classMock addTweet:testTweet];
OCMVerifyAll(classMock);
}
這表示一種友好的mock,不會在沒有OCMExpect或OCMStub設置類的所有方法時拋出異常。以上代碼把OCMExpect和OCMStub注釋掉時不會報錯。
還有一種表示嚴格的mock:OCMStrictClassMock
,如果把OCMExpect和OCMStub注釋掉時會報錯,它要求你執行類中的所有方法,所以比較適合用來測試必須實現的方法,代碼如下:
- (void)testStrictMock3{
id classMock = OCMStrictClassMock([TweetView class]);
//OCMExpect([classMock addTweet:[OCMArg isNotNil]]);
//OCMStub([classMock addTweet:[OCMArg isNotNil]]);
Tweet *testTweet = [[Tweet alloc] init];
testTweet.userName = @"齊滇大圣";
[classMock addTweet:testTweet];
OCMVerifyAll(classMock);
}
參考
ocmock源碼
[iOS單元測試系列]單元測試編碼規范
[iOS單元測試系列]-譯-OCMock常見使用方式
Introduction to mocking with OCMock