純工具類APP已經(jīng)淪為炮灰,移動(dòng)APP幾乎都是基于網(wǎng)絡(luò)的慨代,因此寫(xiě)單元測(cè)試更哄,網(wǎng)絡(luò)是一個(gè)繞不開(kāi)的話題芋齿。實(shí)際iOS開(kāi)發(fā)中腥寇,凡是基于http的網(wǎng)絡(luò)連接, AFNetworking 幾乎已成為一個(gè)標(biāo)準(zhǔn)庫(kù)觅捆,比如發(fā)起一個(gè)post請(qǐng)求赦役,會(huì)創(chuàng)建AFHTTPSessionManager 對(duì)象:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain",@"text/html",nil];
并調(diào)用以下方法:
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
網(wǎng)絡(luò)接口為什么難測(cè)?
還記得《沒(méi)有單元測(cè)試栅炒,何談重構(gòu)》 中的測(cè)試循環(huán)嗎掂摔?讓我們?cè)賮?lái)復(fù)習(xí)一下:
當(dāng)我們?yōu)锳FHTTPSessionManager編寫(xiě)單元測(cè)試時(shí),會(huì)遇到以下問(wèn)題:
1. 返回結(jié)果不可控
雖然我們可以準(zhǔn)備我們想要的目標(biāo)URLString赢赊、parameters等參數(shù)乙漓,但在實(shí)際網(wǎng)絡(luò)中,progress释移、success叭披、failure等block回調(diào)——也就是測(cè)試循環(huán)的“驗(yàn)證結(jié)果”部分——是不確定的。
即使能勉強(qiáng)控制測(cè)試的網(wǎng)絡(luò)環(huán)境玩讳,保證服務(wù)器一定返回success涩蜘,定制返回?cái)?shù)據(jù)也困難重重:準(zhǔn)備把目標(biāo)返回?cái)?shù)據(jù)都寫(xiě)在服務(wù)端?基于真實(shí)的網(wǎng)絡(luò)熏纯,很難獲得理想的數(shù)據(jù)同诫。
鬼知道服務(wù)器會(huì)返回給你什么!
——某開(kāi)發(fā)者云
2. 可能還有經(jīng)濟(jì)上的問(wèn)題
假設(shè)要開(kāi)發(fā)的是對(duì)接獲取驗(yàn)證碼接口的方法樟澜,難道運(yùn)行一次就真的請(qǐng)求一個(gè)短信驗(yàn)證碼剩辟?短信下發(fā)平臺(tái)可是會(huì)¥扣錢¥的。更何況往扔,萬(wàn)一第三方平臺(tái)沒(méi)有響應(yīng)或超時(shí)贩猎,我們的測(cè)試就失敗了,這種異步的萍膛、不確定的測(cè)試吭服,無(wú)論從金錢還是時(shí)間上衡量,都不夠經(jīng)濟(jì)蝗罗,因此很難實(shí)現(xiàn)艇棕。
因此,我們需要有方法指定返回?cái)?shù)據(jù)串塑,并且不需要 實(shí)際訪問(wèn)網(wǎng)絡(luò)沼琉,我們這里選擇使用OCMock。
使用OCMock
OCMock是一個(gè)用于建立仿造對(duì)象的框架桩匪,它使用OC的運(yùn)行時(shí)機(jī)制打瘪,可以自動(dòng)的創(chuàng)建任何OC對(duì)象實(shí)例, 比如可以這樣來(lái)仿制 AFHTTPSessionManager 對(duì)象:
id mockManager = [OCMockObject mockForClass:[AFHTTPSessionManager class]];
準(zhǔn)備數(shù)據(jù)
接著我們就可以使用mockManager提供的 andDo
方法,“偽造”出一個(gè)success調(diào)用:
[[[mockManager expect] andDo:^(NSInvocation *invocation) {
void (^successBlock)(NSURLSessionDataTask *task, id responseObject) = nil;
[invocation getArgument:&successBlock atIndex:5];
successBlock([[NSURLSessionDataTask alloc] init],
@{@"keyTest":@"valueTest"}
);
}] POST:[OCMArg any]
parameters:nil
progress:[OCMArg any]
success:[OCMArg any]
failure:[OCMArg any]];
調(diào)用expect
方法用來(lái)告訴OCMock “接管” POST方法,附加的 andDo
block方法中闺骚,提供了 NSInvocation 對(duì)象彩扔,這使得我們有機(jī)會(huì)指定調(diào)用的block和返回值:
- 先聲明 successBlock ,注意僻爽,格式要與調(diào)用的block聲明一致虫碉;
- 通過(guò) getArgument 指定調(diào)用success方法,atIndex用于指明希望調(diào)用參數(shù)的位置胸梆,這里要從2開(kāi)始敦捧,前兩位分別是self(target),selector(_cmd));
- 調(diào)用successBlock碰镜,傳入希望返回的數(shù)據(jù)绞惦。
注意:[OCMArg any] 用于告訴OCMock,可以接受任何參數(shù)洋措,反之,如果
expert
時(shí)杰刽,POST參數(shù)傳@"aTestPath"菠发,調(diào)用時(shí)任何非@"aTestPath"的POST調(diào)用將被忽略。
調(diào)用接口
[mockManager POST:@"any path"
parameters:nil
progress:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
XCTAssertEqual(@"valueTest", responseObject[@"keyTest"]);
} failure:^(NSURLSessionDataTask * _Nullable task,
NSError * _Nonnull error) {
}];
這時(shí)贺嫂,success block將會(huì)回調(diào)滓鸠,并傳回我們?cè)?code>andDo中指明的字典數(shù)據(jù)@{@"keyTest":@"valueTest"}
到此,我們已經(jīng)有了一個(gè)測(cè)試網(wǎng)絡(luò)應(yīng)用的良好開(kāi)端第喳,基于這里介紹的技術(shù)糜俗,我們可以改造iOS開(kāi)發(fā)中的網(wǎng)絡(luò)接口,使之能更方便編寫(xiě)單元測(cè)試曲饱,歡迎關(guān)注溪石iOS悠抹,留言、私信分享你的感想和經(jīng)驗(yàn)扩淀,共同探討iOS單元測(cè)試中的奇技淫巧楔敌。