[iOS單元測試系列]Singleton如何測試

發表於2015-05-14

Singletion設計模式在cocoa中被廣泛使用。在我們平時寫App程式碼時也經常會將一些工具類,管理類設計成Singletion。Signletion通過一個類方法返回一個唯一的例項,與我們平常通過例項化生成一個個例項的場景有所不同。如果我們要stub一個Singletion的類的例項方法,那麼這個Signletion的類初始化方法(eg:sharedMange())必須返回一個mock物件。因為只有mock物件才可以做stub操作。那麼我們應該如何mock我們的Singletion呢,我們通過下面的例子一步步分析解決這個問題。

Singleton場景

比如我有一個Singleton的類(DemoStatusManage),他有一個例項方法currentStatus會返回一個1-100的隨機數。

然後在我的另外一個類中會去呼叫這個Singletion的currentStatus方法,並且將返回的資料渲染到另外那個類的label文案上。

這是一個很簡單的Singletion場景,但是在測試updateStatusNumber這個API的時候由於依賴到了外部的DemoStatusManage的currentStatus方法,而且這個方法返回的是一個隨機數值,所以我們必須mock掉Singletion,然後再stub調currentStatus方法,讓這個方法返回我們期望的一個固定值。

應該用OCMock的哪個API呢

應該用OCMock的哪個API呢?OCMStrictClassMock(cls)? OCMClassMock(cls)? OCMPartialMock(obj)?

其實這裡按照常規的mock測試一個API都用不上。因為我們mock出來的東西(物件或者是類)只能在我們的測試用例中,updateStatusNumber方法裡面呼叫的永遠是DemoStatusManage的原生類。

那如何才能讓sharedManage不管在哪裡(測試用例中和updateStatusNumber中)都返回我們的mock物件呢,答案是用category重寫sharedManage讓它返回我們的mock物件.

這樣在我們的單元測試類中只要在測試case中初始化一下mock,sharedManage不管在哪裡呼叫就都會返回我們需要的mock物件了。

當然我們也可以讓mock返回一個PartialMock物件。

包裝優化

去掉拷貝的程式碼

你應該也發現了,這段程式碼我們是拷貝過來的。

如果用這種方式,我們會陷入一個問題,我們在維護兩套相同的程式碼,那天app工程中相關的sharedManage的方法有所變動,這裡也要相應的變動。有什麼辦法可以讓它找到原來的IMP實現呢,Matt大神的一篇文章中就告訴我們,Yes,可以的!Supersequent implementation.我們可以用Matt的invokeSupersequentNoArgs()巨集定義來實現這個功能。

這樣我們的Cagegory差不多就長這樣。

包裝mock方法

筆者在用這種方式寫測試用例的時候發現,可能我的UnitTest這個Category是寫在Atest.m中的,但是在沒有寫Category也沒有引用Atest.m的Btest.m中,也會進入到重寫的sharedManage中,而由於mock是static的,也沒有做釋放操作,導致DemoStatusManage永遠是一個mock物件。可能是因為XCTest框架的原因,因為所有的XCTestCase都是沒有.h檔案的,具體原因也不得而知。

所以,要解決這個問題,我們必須在mock使用完畢後釋放它,並且將建立和釋放都包裝出來,提供介面給測試用例呼叫。而且我們可以提供不同型別的mock方式。

這樣我們就可以在使用mock的時候呼叫JTKCreateClassMock 或者 JTKCreatePartialMock: 來生成我們需要的mock物件,在使用完畢後釋放我們的mock物件,就能實現我們的測試需求了。

巨集定義簡化程式碼

我們的工程中不可能只有一個Singletion,少則十幾,多則上百。如果我們對每個Singletion都這麼寫一遍Category的話,這個成本也太他媽大了。而其實不管是哪個Singletion,這個UnitTest的Category都是大同小異的,那麼我們不如寫個巨集定義來簡化我們的程式碼。

這樣我們只需要一行程式碼就能搞定一個Singletion的UnitTest的Category了,來一個寫一行,來一雙寫兩行。

One more thing

Matt文中程式碼可以在github上找到NSObject+SupersequentImplementation

如果使用invokeSupersequentNoArgs()提示Too many arguments to function call,expected 0,have 2,請開啟你的測試工程的target,找到Build Setting下的Enable Strict Checking of objc_mesSend Calls,設定為NO

用category重寫主類中的方法會有一個警告:Category is implementing a method which will also be implemented by its primary class,則使用以下巨集在你重寫的方法前後做個包裝即可

相關文章