iOS 单位测试之常用框架 OCMock 详解

打印 上一主题 下一主题

主题 896|帖子 896|积分 2688

软件测试资料领取:[内部资源] 想拿年薪40W+的软件测试人员,这份资料必须领取~

软件测试面试刷题工具领取:软件测试面试刷题【800道面试题+答案免费刷】

一、单位测试

1.1 单位测试的必要性

测试驱动开发并不是一个很新鲜的概念了。在日常开发中,很多时间需要测试,但是这种输出是必须在点击一系列按钮之后才气在屏幕上显示出来的东西。测试的时间,往往是用模仿器一次一次的从头开始启动 app,然后定位到本身所在模块的程序,做一系列的点击操纵,然后查看效果是否符合本身预期。
这种行为无疑是对时间的巨大浪费。于是有很多资深工程师们发现,我们是可以在代码中构造一个类似的场景,然后在代码中调用我们之前想要检查的代码,并将运行效果和设想效果在程序中进行比力,如果一致,则说明我们的代码没有问题,由此就产生了单位测试。
1.2 单位测试的目的

单位测试的重要目的是发现模块内部逻辑、语法、算法和功能错误。
单位测试重要是基于白盒测试验证以下问题:


  • 验证代码与计划符合度。
  • 发现计划和需求中存在错误。
  • 发现在编码过程中引入的错误。
单位测试关注的重点有以下部分:

独立路径-对于根本实行路径和循环进行测试,大概的错误有:


  • 不同数据类型的比力。
  • “差1错”,即大概多循环或少循环一次。
  • 错误或不大概的停止条件。
  • 不得当的修改了循环变量。
局部数据布局-单位的局部数据布局是最常见的错误来源,应计划测试用例以检查大概的错误:


  • 不一致的数据类型。
  • 检查不精确或不一致的数据类型。
错误处理-比力完善的单位计划要能预见堕落的条件,并设置得当的错误处理,以便在程序堕落时,能对错误重新做安排,包管期逻辑上的精确性:


  • 堕落的形貌难以明白。
  • 显示的错误与现实的错误不符。
  • 对错误条件的处理不精确。
界限条件-界限上出现错误是最常见的错误现象:


  • 取最大最小值发生错误。
  • 控制流中的大于、小于这些比力值常出现错误。
单位接口-接口现实上就是输入和输出对应关系的集合,要对单位进举措态测试无非就是给这个单位一个输入,然后检查输出是否和预期一致。如果数据不能正常输入和输出,单位测试就无从谈起,因此需要对单位接口进行如下的测试:


  • 被测单位的输入、输出在个数、属性、次序是否和详细计划中的形貌一致。
  • 是否修改了只做输入用的形式参数。
  • 约束条件是否通过形式参数来传送。
1.3 单位测试依赖的两个重要框架

OCUnit(即用 XCTest 进行测试)实在就是苹果自带的测试框架,重要是断言利用,由于利用简单本次文章不过多介绍。
OCMock重要功能是模仿某个方法大概属性的返回值,你大概会疑惑为什么要如许做?利用模型天生的模型对象,再传进去不就可以了?答案是可以的,但是有特殊的情况,比如一些不容易构造或不容易获取的对象,此时你可以创建一个虚拟的对象来完成测试。实现思想是根据要mock的对象的class来创建一个对应的对象,并且设置好该对象的属性和调用预定方法后的动作(例如返回一个值,调用代码块,发送消息等等),然后将其记录到一个数组中,接下来开发者主动调用该方法,末了做一个verify(验证),从而判断该方法是否被调用,大概调用过程中是否抛出非常等。在单位测试开发中利用更多难点的也是对OCMock的利用方式不明确,本次文章重要讲的就是这个 OCMock 的集成和利用方法。
二、OCMock 的集成与利用

2.1 OCMock 的集成方式

项目集成 OCMock 第三方库,这个利用 pod 工具直接安装OCMock框架即可。若利用 iBiu 工具安装 OCMock 库需在 podfile 文件同级创建 Podfile.custom。

利用普通的 pod 文件雷同格式添加 OCmock 如下:
  1. source 'https://github.com/CocoaPods/Specs.git'
  2. pod 'OCMock'
复制代码
2.2 OCMock 的利用方法

(一)置换方法(存根):告诉 mock 对象,当 someMethod 被调用,返回什么值
调用方式:
  1. d jalopy = [OCMock mockForClass[Car class]];
  2. OCMStub([jalopy goFaster:[OCMArg any] units:@"kph"]).andReturn(@"75kph");
复制代码
利用场景:
1. 验证 A 方法时,A 方法内部利用 B 方法的返回值但是 B 方法内部逻辑比力复杂,这时需要利用 stub 方法去存根 B 方法的返回值。代码实现类似下面代码实现固定 funcB 的返回值,做到在不影响源代码的条件下,获取满足测试需要的参数。
方法进行存根前
  1. - (NSString *)getOtherTimeStrWithString:(NSString *)formatTime{
  2. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  3. [formatter setDateStyle:NSDateFormatterMediumStyle];
  4. [formatter setTimeStyle:NSDateFormatterShortStyle];
  5. [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"]; //(@"YYYY-MM-dd hh:mm:ss") ----------设置你想要的格式,hh与HH的区别:分别表示12小时制,24小时制
  6. //设置时区选择北京时间
  7. NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:@"Asia/Beijing"];
  8. [formatter setTimeZone:timeZone];
  9. NSDate* date = [formatter dateFromString:formatTime]; //------------将字符串按formatter转成nsdate
  10. //时间转时间戳的方法:
  11. NSInteger timeSp = [[NSNumber numberWithDouble:[date timeIntervalSince1970]] integerValue] * 1000;
  12. return [NSString stringWithFormat:@"%ld",(long)timeSp];
  13. }
复制代码
利用stub(mockObject getOtherTimeStrWithString).andReturn(@“1000”)存根后类似于以下效果
  1. [/code] [code]
  2. - (NSString *)getOtherTimeStrWithString:(NSString *)formatTime{
  3. return @"1000";
  4. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  5. [formatter setDateStyle:NSDateFormatterMediumStyle];
  6. [formatter setTimeStyle:NSDateFormatterShortStyle];
  7. [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"]; //(@"YYYY-MM-dd hh:mm:ss") ----------设置你想要的格式,hh与HH的区别:分别表示12小时制,24小时制
  8. //设置时区选择北京时间
  9. NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:@"Asia/Beijing"];
  10. [formatter setTimeZone:timeZone];
  11. NSDate* date = [formatter dateFromString:formatTime]; //------------将字符串按formatter转成nsdate
  12. //时间转时间戳的方法:
  13. NSInteger timeSp = [[NSNumber numberWithDouble:[date timeIntervalSince1970]] integerValue] * 1000;
  14. return [NSString stringWithFormat:@"%ld",(long)timeSp];
  15. }
复制代码
2. 代码正常流程经过测试已经很结实了,但是一些错误的流程并不容易发现但是是大概存在的,例如边沿值数据,单位测试中可以利用存根对数据进行模仿,测试代码在特殊数据情况下的运行情况。
注:stub()也可以不设置返回值,验证可行,猜测大概是返回的nil大概void,所以不带返回值的方法也可以进行方法存根。
(二)天生 Mock 对象,目前有三种方式。
通过对Person类的talk方法进行测试举例,其中也涉及Men类以及Animaiton类,以下是三个类的相干源码。
Person类
  1. @interface Person()
  2. @property(nonatomic,strong)Men *men;
  3. @end
  4. @implementation Person
  5. -(void)talk:(NSString *)str
  6. {
  7. [self.men logstr:str];
  8. [Animaiton logstr:str];
  9. }
  10. @end
复制代码
Men类
  1. @implementation Men
  2. -(NSString *)logstr:(NSString *)str
  3. {
  4. NSLog(@"%@",str);
  5. return str;
  6. }
  7. @end
复制代码
Animaiton类
  1. @implementation Animaiton
  2. +(NSString *)logstr:(NSString *)str
  3. {
  4. NSLog(@"%@",str);
  5. return str;
  6. }
  7. -(NSString *)logstr:(NSString *)str
  8. {
  9. NSLog(@"%@",str);
  10. return str;
  11. }
  12. @end
复制代码
对talk方法进行单测时需要对person类进行mock,以下是通过三种不同的方式天生mock对象,对三种方式的调用方法,利用场景都做了介绍,末了对每种方式的优缺点也做了一个表格方便区别。
Nice Mock

NiceMock 创建的 mock 对象在进行方法测试时会优先调用实例方法,若未找到实例方法,会继续调用同名的类方法。因此该方法可以用来天生mock对象去测试类方法也可以测试对象方法。
利用方式:
  1. - (void)testTalkNiceMock {
  2. id mockA = OCMClassMock([Men class]);
  3. Person *person1 = [Person new];
  4. person1.men = mockA;
  5. [person1 talk:@"123"];
  6. OCMVerify([mockA logstr:[OCMArg any]]);
  7. }
复制代码
利用场景:
Nice mock 是比力友爱的,当一个没有存根的方法被调用时他不会引起一个非常会验证通过。如果你不想本身对很多的方法进行存根,那么利用 nice mock。在上方的举例中mockA调用testTalkNiceMock时,Men类中的+(NSString *)logstrNSString *)str不会实行打印操纵。在调用过程中因为同时存在同名的logstr:类方法和实例方法,会优先调用实例方法。
Strict Mock

利用方式:
测试case如下,mockA是Strict Mock天生要调用testTalkStrictMock方法,则Mock天生要调用testTalkStrictMock方法则该方法要利用stub进行存根,否则末了的OCMVerifyAll(mockA)就会抛出非常。
  1. - (void)testTalkStrictMock {
  2. id mockA = OCMStrictClassMock([Person class]);
  3. OCMStub([mockA talk:@"123"]);
  4. [mockA talk:@"123"];
  5. OCMVerifyAll(mockA);
  6. }
复制代码
利用场景:
这种方式创建的 mock 对象,如果调用未 stub(stub 代表存根)的方法,会抛出一个非常。这需要包管在 mock 的生命周期中每一个独立调用的方法都是被存根的,这种方法利用比力严格,很少利用。
Partial Mock

如许创建的对象在调用方法时:如果方法被 stub,调用 stub 后的方法,如果方法没有被 stub,调用原来的对象的方法,该方法有限制只能 mock 实例对象。
利用方式:
  1. - (void)testTalkPartialMock {
  2. id mockA = OCMPartialMock([Men new]);
  3. Person *person1 = [Person new];
  4. person1.men = mockA;
  5. [person1 talk:@"123"];
  6. OCMVerify([mockA logstr:[OCMArg any]]);
  7. }
复制代码
利用场景:
当调用一个没有被存根的方法时,会调用现实对象的该方法。当不能很好的存根一个类的方法时,该技能是非常有用的。调用testTalkPartialMock时Men类中的+(NSString *)logstrNSString *)str会实行打印操纵。
三种方式的差异表格:

(三)验证方法的调用
调用方式:
  1. OCMVerify([mock someMethod]);
  2. OCMVerify(never(), [mock doStuff]); //从没被调用
  3. OCMVerify(times(n), [mock doStuff]); //调用了N次
  4. OCMVerify(atLeast(n), [mock doStuff]); //最少被调用了N次
  5. OCMVerify(atMost(n), [mock doStuff]);
复制代码
利用场景:
在单位测试中可以验证某个方法是否实行,以及实行了反复。
延时验证调用:
  1. OCMVerifyAllWithDelay(mock, aDelay);
复制代码
利用场景:该功能用于等待异步操纵会比力多,其中aDelay为预期最长等待时间。
(四)添加预期
调用方式:
准备数据:
  1. NSDictionary *info = @{@"name": @"momo"};
  2. id mock = OCMClassMock([MOOCMockDemo class]);
复制代码
添加预期:
  1. OCMExpect([mock handleLoadSuccessWithPerson:[OCMArg any]]);
复制代码
可以预期不实行:
  1. OCMReject([mock handleLoadFailWithPerson:[OCMArg any]]);
复制代码
可以验证参数:
  1. // 预期 + 参数验证
  2. OCMExpect([mock handleLoadSuccessWithPerson:[OCMArg checkWithBlock:^BOOL(id obj) {
  3. MOPerson *person = (MOPerson *)obj;
  4. return [person.name isEqualToString:@"momo"];
  5. }]]);
复制代码
可以预期实行次序:
  1. // 预期下列方法次序实行[mock setExpectationOrderMatters:YES];OCMExpect([mock handleLoadSuccessWithPerson:[OCMArg any]]);
  2. OCMExpect([mock showError:NO]);
复制代码
可以忽略参数(预期方法实行时):
  1. OCMExpect([mock showError:YES]).ignoringNonObjectArgs; // 忽视参数
复制代码
实行:
  1. [MOOCMockDemo handleLoadFinished:info];
复制代码
断言:
  1. OCMVerifyAll(mock);
复制代码
可以耽误断言:
  1. OCMVerifyAllWithDelay(mock, 1); // 支持延迟验证
复制代码
末了的 OCMVerifyAll 会验证前面的期望是否有用,只要有一个没调用,就会堕落。
(五)参数约束
调用方式:
  1. OCMStub([mock someMethodWithAnArgument:[OCMArg any]])
  2. OCMStub([mock someMethodWithPointerArgument:[OCMArg anyPointer]])
  3. OCMStub([mock someMethodWithSelectorArgument:[OCMArg anySelector]])
复制代码
利用场景:在利用 OCMVerify()方法验证某个方法是否调用是利用,单位测试会验证方法参数是否一致,如果不一致就是提示验证失败,此时如果只关注方法调用,并不关注参数即可利用[OCMArg any]传参。
(六)网络接口的模仿
顾名思义可以 mock 网络接口的数据返回,测试不同数据下代码的走向以及准确性。
调用方式:
  1. id mockManager = OCMClassMock([JDStoreNetwork class]);
  2. [orderListVc setComponentsNet:mockManager];
  3. [OCMStub([mockManager startWithSetup:[OCMArg any] didFinish:[OCMArg any] didCancel:[OCMArg any]]) andDo:^(NSInvocation *invocation) {
  4. void (^successBlock)(id components,NSError *error) = nil;
  5. [invocation getArgument:&successBlock atIndex:3];
  6. successBlock(@{@"code":@"1",@"resultCode":@"1",@"value":@{@"showOrderSearch":@"NO"}},nil);
  7. }];
复制代码
以上就是在调用 setComponentsNet 方法内部调用了接口,该方法就可以在调用接口后模仿需要的返回数据,successBlock 中的就是返回的测试数据。本方式是通过获取接口调用的方法签名,获取 successBlock 成功回调传参并手动调用。同样可以模仿接口失败的情况,只需获取到签名中的对应的失败回调就可以实现了。
利用场景:书写单位测试方法时涉及网络接口的模仿,通过该方式 mock 接口返回效果。
(七)恢复类
置换类方法后,可以将类恢复到原来的状态,通过调用 stopMocking 来完成。
调用方式:
  1. id classMock = OCMClassMock([SomeClass class]);
  2. /* do stuff */
  3. [classMock stopMocking];
复制代码
利用场景:
正常对实例对象置换后,mock 对象释放后会主动调用 stopMocking,但是添加到类方法上的 mock 对象会凌驾了多个测试,mock 的类对象在置换后不会 deallocated,需要手动来取消这个 mock 关系。
(八)观察者模仿-创建一个担当通知的实例
调用方式:
  1. - (void)testPostNotification {
  2. Person *person1 = [[Person alloc] init];
  3. id observerMock = OCMObserverMock();
  4. //给通知中心设置观察者
  5. [[NSNotificationCenter defaultCenter] addMockObserver: observerMock name:@"name" object:nil];
  6. //设置观察期望
  7. [[observerMock expect] notificationWithName:@"name" object:[OCMArg any]]; //调用要验证的方法
  8. [person1 methodWithPostNotification];
  9. [[NSNotificationCenter defaultCenter] removeObserver:observerMock];
  10. // 调用验证
  11. OCMVerifyAll(observerMock);}
复制代码
利用场景:
创建一个 mock 对象,可以用来观察通知。mock 必须注册以接收通知。
(九)mock协议
调用方式:
  1. id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));
  2. /*严格的协议*/
  3. id classMock = OCMStrictClassMock([SomeClass class]);
  4. id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));
  5. id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));
  6. /*严格的协议*/
  7. id classMock = OCMStrictClassMock([SomeClass class]);
  8. id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));
复制代码
调用场景:当需要创建一个实例,让其具有协议的所定义的功能时利用。
2.3 mock利用限制

对于同个方法,先stub后expect是不可的:因为先stub的话,全部的调用都会酿成stub,如许子即使过程调用该方法,末了OCMVerifyAll验证也会失败;解决的办法是,在OCMExpect上顺便stub,比如:OCMExpect([mock someMethod]).andReturn(@“a string”),大概将stub置于expect之后。
部分模仿不适用于某些类:如NSString和NSDate,这些”toll-free bridged”的类,否则会抛出非常。
某些方法不能stub:如:init、class、methodSignatureForSelector、forwardInvocation这些。
NSString与NSArray的类方法不能stub,否则无效。
NSObject的方法调用不能验证,除非在子类中重写。
苹果焦点类的私有方法调用不能被验证,如以_开头的方法。
延时验证方法调用不支持,临时只支持期望-运行-验证模式的延时验证。
OCMock不支持多线程。
三、末了

盼望这篇文章和例子已经陈述清楚了一些 OCMock 最通用的用法。OCMock 站点:http://ocmock.org/features/ 是一个最好的学习 OCMock 的地方。mock 是单调的但是对于一个应用程序却是必须的。如果一个方法很难用 mock 来测试,这个迹象表明你的计划需要重新思量了。
既然看到这里,在收藏的同时,也请不吝啬的点个赞呗!期待 ~

末了感谢每一个认真阅读我文章的人,下方这份完整的软件测试教程已经整理上传完成,需要的朋侪们可以文末自行领取:【包管100%免费】


这些资料,对于【软件测试】的朋侪来说应该是最全面最完整的备战仓库,这个仓库也伴随上万个测试工程师们走过最艰难的路程,盼望也能资助到你!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

科技颠覆者

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表