iOS單元測試(一)如何Mock-Singleton 單例測試

  • Mock單例

單例模式是我們在iOS中最常使用的設(shè)計(jì)模式之一。單例模式不需要傳遞任何參數(shù)定铜,它通過一個(gè)類方法返回一個(gè)唯一的實(shí)例伪冰,與我們平常通過實(shí)例化生成一個(gè)個(gè)實(shí)例的場景有所不同。那么我們要stub一個(gè)單例的類的實(shí)例方法話是必須要返回一個(gè)mock對象鹏浅,因?yàn)橹挥衜ock對象才可以做stub操作。那么我們應(yīng)該如何mock單例類呢屏歹,下面有一段宏主要目的是通過category重寫sharedManage讓它返回我們的mock對象隐砸,這樣只要在測試case中初始化一下mock,sharedManage不管在哪里調(diào)用就都會(huì)返回我們需要的mock對象了蝙眶。
好下面我們開始一步一步分析此宏并運(yùn)用到項(xiàng)目中季希。

#define JTKMOCK_SINGLETON(__className,__sharedMethod)               \
JTKMOCK_SINGLETON_CATEGORY_DECLARE(__className)                     \
JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(__className,__sharedMethod)    \


#define JTKMOCK_SINGLETON_CATEGORY_DECLARE(__className)         \
\
@interface __className (UnitTest)                               \
\
+ (instancetype)JTKCreateClassMock;                             \
\
+ (instancetype)JTKCreatePartialMock:(__className *)obj;        \
\
+ (void)JTKReleaseMock;                                         \
\
@end


#define JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(__className,__sharedMethod)    \
\
static __className *mock_singleton_##__className = nil;                     \
\
@implementation __className (UnitTest)                                      \
\
+ (instancetype)__sharedMethod {                                            \
if (mock_singleton_##__className) return mock_singleton_##__className;  \
return invokeSupersequentNoParameters();                             \
}                                                                           \
+ (instancetype)JTKCreateClassMock {                                        \
mock_singleton_##__className = OCMClassMock([__className class]);       \
return mock_singleton_##__className;                                    \
}                                                                           \
\
+ (instancetype)JTKCreatePartialMock:(__className *)obj {                   \
mock_singleton_##__className = OCMPartialMock(obj);                     \
return mock_singleton_##__className;                                    \
}                                                                           \
\
+ (void)JTKReleaseMock {                                                    \
mock_singleton_##__className = nil;                                     \
}                                                                           \
\
@end

好我們來看第一段


#define JTKMOCK_SINGLETON(__className,__sharedMethod)               \
JTKMOCK_SINGLETON_CATEGORY_DECLARE(__className)                     \
JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(__className,__sharedMethod)    \

首先定義了JTKMOCK_SINGLETON宏有倆個(gè)參數(shù), 第一個(gè)__className是類名第二個(gè)參數(shù)是方法名,這里先補(bǔ)充一下宏知識(shí)為什么要加雙下劃線幽纷,因?yàn)檫@樣是為了避免變量名相同而導(dǎo)致問題的可能性式塌,后面的 \ 是什么?它代表行繼續(xù)操作符友浸,當(dāng)定義的宏不能用一行表達(dá)完整時(shí)峰尝,可以用""表示下一行繼續(xù)此宏的定義。還有下邊我們用到的 ## 是一個(gè)符號(hào)連接操作符它是用于連接作用收恢。

JTKMOCK_SINGLETON_CATEGORY_DECLARE 這個(gè)是聲明分類宏
JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT 這個(gè)是實(shí)現(xiàn)宏

接下來我們繼續(xù)往下看


#define JTKMOCK_SINGLETON_CATEGORY_DECLARE(__className)         \
\
@interface __className (UnitTest)                               \
\
+ (instancetype)JTKCreateClassMock;                             \
\
+ (instancetype)JTKCreatePartialMock:(__className *)obj;        \
\
+ (void)JTKReleaseMock;                                         \
\
@end

定義了三個(gè)類方法:

  • JTKCreateClassMock

  • JTKCreatePartialMock

  • JTKReleaseMock

Mock類型分為ClassMock武学,PartialMock

  1. ClassMock
    如果你不想stub很多方法,而且不會(huì)在一個(gè)沒有stub掉的方法被調(diào)用的時(shí)候拋出異撑晌埽可以使用ClassMock劳淆。
  2. PartialMock
    如果沒有stub掉的方法被調(diào)用了,這個(gè)方法會(huì)被轉(zhuǎn)發(fā)到真實(shí)的對象上默赂。這是對mock技術(shù)上的欺騙沛鸵,但是非常有用,當(dāng)有一些類不適合讓自己很好的被stub時(shí)可以用PartialMock。

下面我們看實(shí)現(xiàn)代碼:
首先創(chuàng)建一個(gè)靜態(tài)全局變量在測試中我們可以使用此對象

#define JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(__className,__sharedMethod)    \

static __className *mock_singleton_##__className = nil;                     \
\
@implementation __className (UnitTest)                                      \
\
+ (instancetype)__sharedMethod {                                            \
if (mock_singleton_##__className) return mock_singleton_##__className;  \
return invokeSupersequentNoParameters();                             \
}   

此時(shí) ## 起到連接變量名作用


例:
JTKMOCK_SINGLETON_CATEGORY_IMPLEMENT(CDUserManager曲掰,sharedManager)

宏展開后:
static CDUserManager *mock_singleton_CDUserManager = nil;                     
@implementation CDUserManager (UnitTest)                                      
+ (instancetype) sharedManager {                                            
if (mock_singleton_CDUserManager) return mock_singleton_CDUserManager;  
return invokeSupersequentNoParameters();                             
} 

我們看一下sharedManager

if (mock_singleton_CDUserManager) return mock_singleton_CDUserManager;
return invokeSupersequentNoParameters(); 
首先判斷一下是否有mock對象疾捍,如果有則返回mock對象沒有則調(diào)用return

invokeSupersequentNoParameters它是一個(gè)宏,其作用是利用runtime查找原始實(shí)現(xiàn)方法IMP指針栏妖,然后調(diào)用原始單例生成對象乱豆。在這里調(diào)用此宏目的是如果原始的sharedManage的方法有所變動(dòng)此宏也不用改動(dòng)任何代碼,不用維護(hù)相同的倆套單例代碼詳細(xì)原理在Matt大神的文章中可以找到它吊趾。

下面是分別創(chuàng)建ClassMock與PartialMock對象宛裕,具體用法詳見Demo。

+ (instancetype)JTKCreateClassMock {                                        \
mock_singleton_##__className = OCMClassMock([__className class]);       \
return mock_singleton_##__className;                                    \
}                                                                           \
\
+ (instancetype)JTKCreatePartialMock:(__className *)obj {                   \
mock_singleton_##__className = OCMPartialMock(obj);                     \
return mock_singleton_##__className;                                    \
}                                                                           \
\
+ (void)JTKReleaseMock {                                                    \
mock_singleton_##__className = nil;                                     \
}                                                                           \
\
@end
  • 如果調(diào)用宏 JTKMOCK_SINGLETON 提示 Too many arguments to function call,expected 0,have 2,請打開你的測試工程的target论泛,找到Build Setting下的Enable Strict Checking of objc_mesSend Calls,設(shè)置為NO
  • 好至此宏代碼分析完了下面我們新建個(gè)Demo看看如何使用去測試單例方法揩尸。
#import <XCTest/XCTest.h>
#import "HQYBaseTestCase.h"
#import "HQYSharedManager.h"
#import "ViewController.h"

//用category重寫主類中的方法會(huì)有一個(gè)警告用以下代碼包裝去除
#pragma clang diagnostic push                                       
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

JTKMOCK_SINGLETON(HQYSharedManager, sharedManager)

#pragma clang diagnostic pop


@interface DemoMockSingletonTest : HQYBaseTestCase
{
    ViewController *_vc;
    ViewController *_vcMock;
}
@end

@implementation DemoMockSingletonTest

- (void)setUp {
    [super setUp];
    
    _vc = [ViewController new];
    _vcMock = OCMPartialMock(_vc);
    
   // [HQYSharedManager JTKCreateClassMock];
   // 這里使用PartialMock
    [HQYSharedManager JTKCreatePartialMock:[HQYSharedManager sharedManager]];

   // 然后使用mock后的對象sutb掉loadCurrentUserName讓其返回我們期望值
    OCMStub([mock_singleton_HQYSharedManager loadCurrentUserName]).andReturn(@"stub success");
    
}
// 清空
- (void)tearDown {
    _vc = nil;
    _vcMock = nil;
    [HQYSharedManager JTKReleaseMock];
    [super tearDown];
}
// Stub測試
- (void)testLoadCurrentUserName{
    NSString *userName = [[HQYSharedManager sharedManager] loadCurrentUserName];
    XCTAssertEqual(userName, @"stub success");
}
// 控制器測試
- (void)testViewControllerLoadUserName{
    [_vc settingUserName];
    XCTAssertTrue([_vc.currentUserName isEqualToString:@"stub success"],@"沒有成功賦值 錯(cuò)誤顯示:%@",_vc.currentUserName);
}
@end

  • 如有理解錯(cuò)誤請立即聯(lián)系修改以免誤導(dǎo)他人謝謝。
  • 本文 Demo下載
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屁奏,一起剝皮案震驚了整個(gè)濱河市岩榆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坟瓢,老刑警劉巖勇边,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異折联,居然都是意外死亡粒褒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門诚镰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怀浆,“玉大人,你說我怎么就攤上這事怕享≈瓷模” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵函筋,是天一觀的道長沙合。 經(jīng)常有香客問我,道長跌帐,這世上最難降的妖魔是什么首懈? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谨敛,結(jié)果婚禮上究履,老公的妹妹穿的比我還像新娘。我一直安慰自己脸狸,他們只是感情好最仑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布藐俺。 她就那樣靜靜地躺著,像睡著了一般泥彤。 火紅的嫁衣襯著肌膚如雪欲芹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天吟吝,我揣著相機(jī)與錄音菱父,去河邊找鬼。 笑死剑逃,一個(gè)胖子當(dāng)著我的面吹牛浙宜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛹磺,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼梆奈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了称开?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤乓梨,失蹤者是張志新(化名)和其女友劉穎鳖轰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扶镀,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蕴侣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了臭觉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昆雀。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝠筑,靈堂內(nèi)的尸體忽然破棺而出狞膘,到底是詐尸還是另有隱情,我是刑警寧澤什乙,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布挽封,位于F島的核電站,受9級特大地震影響臣镣,放射性物質(zhì)發(fā)生泄漏辅愿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一忆某、第九天 我趴在偏房一處隱蔽的房頂上張望点待。 院中可真熱鬧,春花似錦弃舒、人聲如沸癞埠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燕差。三九已至遭笋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間徒探,已是汗流浹背瓦呼。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留测暗,地道東北人央串。 一個(gè)月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像碗啄,于是被迫代替她去往敵國和親质和。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Singletion設(shè)計(jì)模式在cocoa中被廣泛使用稚字。在我們平時(shí)寫App代碼時(shí)也經(jīng)常會(huì)將一些工具類饲宿,管理類設(shè)計(jì)成S...
    木易林1閱讀 276評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)胆描,斷路器瘫想,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • Singletion設(shè)計(jì)模式在cocoa中被廣泛使用。在我們平時(shí)寫App代碼時(shí)也經(jīng)常會(huì)將一些工具類昌讲,管理類設(shè)計(jì)成S...
    子循_陳奕龍閱讀 825評論 0 2
  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡單国夜、最易理解的設(shè)計(jì)模式,也因?yàn)樗暮啙嵰锥坛瘢琼?xiàng)目中最...
    成熱了閱讀 4,253評論 4 34
  • 第一次和臺(tái)灣人說“謝謝”的時(shí)候车吹,那個(gè)女生很是余音裊裊地回了“不會(huì)〈妆眨”最后那個(gè)元音是如此婉轉(zhuǎn)以至于我回憶學(xué)習(xí)了...
    蒼蒼蒼閱讀 1,196評論 1 3