跳至主要内容

OCMock的学习

原文链接
官网设置方法

关于OCMock的学习

在写单元测试时,不可避免的要尽可能少地实例化一些具体的组件,来保持测试既短又快.我们会使用mock来替代实例化具体的依赖对象.mock是指在测试中用一个伪造的有预定义行为的具体对象的替身对象,而被测试的对象不知道两者的差异.官网点这里
本篇文章书写时,是基于OCMock的3.3版本的API的。

1. 创建Mock对象

  1. Class mocks
     id classMcok = OCMClassMock([SomeClass class]);
    
    创建一个mock对象,可以当做SomeClass的实例来使用.可以mock类的实例,也可以mock类以使用类方法,在下面将会提到.
  2. Protocol mocks
     id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));
    
    类型mock类的对象一样,protocolMock可以像SomeProtocol的具体实现一样进行使用.
  3. Strict class and protocol mocks
     id classMock = OCMStrictClassMock([SomeClass class]);
     id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));
    
    创建一个严格的mock对象. mock对象会对将要执行的方法进行一定预测,如果没有设定预测,mock对象一般会返回nil或者返回类型的默认值.但严格的mock对象,规定了如果没有进行预测设定,而调用了这些方法,就会抛出异常,后面还会详细介绍.
  4. Partial mocks
     id partialMock = OCMPartialMock(anObject);
    
    创建一个anObject的mock对象,对于这个mock对象,如果设置了stub方法,对mock对象调用方法,mock对象会执行这个方法.如果对真实对象调用方法,依旧会执行mock对象的stub方法.而如果没有设置stub的方法,调用时会转发给真实对象. 下面会详解partial mocks
  5. Observer mocks
     id observerMock = OCMObserverMock();
    
    创建一个可以监听Notifications的mock,mock对象要注册监听的消息.下面会有详细介绍.

2. stubbing methods

stub方法,即对方法进行预测,如设置返回值,设置调用逻辑,抛出异常等等.设置stub后,调用函数时,原函数不再执行,而以stub的逻辑来进行交互.
  1. 返回对象
     OCMStub([mock someMethod]).andReturn(anObject);
    
    告诉mock对象,在someMethod方法调用时,以固定返回值返回.
  2. 返回非对象类型
     OCMStub([mock someMethod]).andReturn(YES);
    
    与返回对象类似,区别在于,如果传的值与方法的返回值类型不同,且不是简单可以替换的类型类型的话,如int和long的互换,则会抛出错误,
  3. 执行时调用其他方法
     OCMStub([mock someMethod]).andCall(anotherObject, @selector(aDifferentMethod));
    
    当mock对象的someMethod方法调用时,会调用anotherObject对象的aDifferentMethod方调用方法其实是替换实现,将mock方法调用时的参数传递给anotherObject对象的方法,并拿这个aDifferentMethod方法的返回值作为返回值返回.对于那些参数类型是对象类型或者其他类型的,可以用OCMArg的函数来忽略参数,即不处理参数,直接传递给aDifferentMethod函数
     + (id)any;
     + (SEL)anySelector;
     + (void *)anyPointer;
     + (id __autoreleasing *)anyObjectRef;
    
    但是对于参数类型中有非对象类型的参数,如基础数据类型int,Bool等,必须使用ignoringNonObjectArgs来传递参数了.
         // 对象中
         - (long)fbool:(long) b {
             return 2 + b;
         }
         // self
         - (long)fbool:(long)b {
             return 10 + b;
         }
         // 忽略参数,直接调用self的fbool函数
         [[[[partialMock stub] ignoringNonObjectArgs] andCall:@selector(fbool:) onObject:self] fbool:0];
         f = [partialMock fbool:12];
         // 最终输出为 22.
         NSLog(@"andCall ---- f == %@",@(f));      
    
  4. 调用block
     OCMStub([mock someMethod]).andDo(^(NSInvocation *invocation)
     { /* block that handles the method invocation */ });
    
    在方法调用时,将参数以NSInvocation的形式进行封装,传递给block进行一些附加操作.由于传递的是NSInvocation,所以可以在block中来设置调用的返回值.
  5. 设置参数中的返回值
     OCMStub([mock someMethodWithReferenceArgument:[OCMArg setTo:anObject]]);
     OCMStub([mock someMethodWithReferenceArgument:[OCMArg setToValue:OCMOCK_VALUE((int){aValue})]]);
    
    这种mock的情况,是对于函数返回值放在参数中的情况,即参数传递的是一个 指针的指针,或者 void *指针的这种情况,直接不在执行mock函数,而是将返回值直接设置给返回值参数.所以这个函数的意思就是 将参数设置为一个对象或者一个值. 举简单的例子说明一下吧:
     // mock对象拥有这个函数.
     - (void)set:(NSString **)strptr {
         *strptr = @"hello world";
     }
    
     NSString *mocked = @"mocked";
     NSString *arguments;
     OCMStub([partialMock set:[OCMArg setTo:mocked]]);
       
     [partialMock set:&arguments];
        
     NSLog(@"arguments: %@" ,arguments );
     NSLog(@"mocked: %@" ,mocked );
    
    最终的输出是:
     arguments: mocked
     mocked: mocked
    
    set函数没有调用,直接将mocked的值设置给了arguments. 这种返回值在参数中的,一般用在 传递NSError的函数中.对于下面的值的设置,设置的值是基础类型而不是对象,如:
     - (void)set:(int *)kk {
         *kk = 12;
     }
    
     OCMStub([partialMock set:[OCMArg setToValue:OCMOCK_VALUE(11)]]);
    
  6. 执行block参数
     OCMStub([mock someMethodWithBlock:[OCMArg invokeBlock]]);
     OCMStub([mock someMethodWithBlock:([OCMArg invokeBlockWithArgs:@"First arg", nil])]);
    
    mock对象在调用stub方法时,传入一个block类型的参数, 这个时候进行stub,这个block将被执行.对于有参数的block,可以通过invokeBlockWithArgs来设置block的参数,如果没有设置参数,则以该类型的参数默认值传入参数,如NSString 是null,数值类型是0等.
     - (void)doBlock:(void (^)(NSString *)) blk{
         blk(@"doBlock");
         NSLog(@"doblock");
     }
    
     void (^argblk)(NSString *) = ^(NSString *str){
         NSLog(@"argblk -- %@",str);
     };
     // 这种情况下,传递的参数为空,输出日志为argblk -- (null)
     OCMStub([partialMock doBlock:[OCMArg invokeBlock]]);
     // 指定block的参数,输出日志为 argblk -- hello
     OCMStub([partialMock doBlock:([OCMArg invokeBlockWithArgs:@"hello", nil])]);
     [partialMock doBlock:argblk];
    
  7. 扔出一个异常
     OCMStub([mock someMethod]).andThrow(anException);
    
  8. 发送一个消息
     OCMStub([mock someMethod]).andPost(aNotification);
    
  9. 链式编程
     OCMStub([mock someMethod]).andPost(aNotification).andReturn(aValue);
    
    这里所有的操作都是可以链式传递的,所以调用函数时,可以同时执行block,发送消息,设定返回值等等.
  10. 消息传递给真实的对象
    OCMStub([mock someMethod]).andForwardToRealObject();
    
    只有在使用partial mock或者mock 类方法时,才能发消息给真的对象. 一般用在链式编程中,或者使用期望的时候.
  11. 什么也不做
    OCMStub([mock someMethod]).andDo(nil);
    
    注意,这个方法也只能在partial mock或者mock 类方法时使用. 传递一个空的block在函数调用时执行.

3. 验证相互调用

id mock = OCMClassMock([SomeClass class]);

/* run code under test */

OCMVerify([mock someMethod]);
使用 OCMVerify来判断函数是否执行过,可以在OCMVerify中添加参数判断,即判断函数以传入符合要求的参数被调用.如:
id mock = OCMClassMock([MC class]);
OCMStub([mock fbool:2]).andDo(^(NSInvocation *invocation){
    long x;
    [invocation getArgument:&x atIndex:2];
    NSLog(@"输入 - x - : %@",@(x));
});
[mock fbool:2];
OCMVerify([mock fbool:1]);
这里校验函数是否以参数为1的情况进行调用,当然这里没有,测试会失败.至于函数参数的约束,在下一节详细介绍:

4. 参数约束

参数约束,是指在符合约束的情况下,才会允许调用发送,否则会忽略调用.
  1. any ,不做约束
     OCMStub([mock someMethodWithAnArgument:[OCMArg any]])
     OCMStub([mock someMethodWithPointerArgument:[OCMArg anyPointer]])
     OCMStub([mock someMethodWithSelectorArgument:[OCMArg anySelector]])
    
  2. 非对象类型的忽略
     [[[mock stub] ignoringNonObjectArgs] someMethodWithIntArgument:0]
    
    这个时候,OCMock就没有提供宏给我们使用了,上面对于对象来说,可以设置一些参数忽略检测,一些参数进行检测,但是这个非对象类型,设置这个ignoringNonObjectArgs后,这个函数所有的非对象类型就全部被忽略了.
  3. 对象类型的检测
     OCMStub([mock someMethod:aValue)
     OCMStub([mock someMethod:[OCMArg isNil]])
     OCMStub([mock someMethod:[OCMArg isNotNil]])
     OCMStub([mock someMethod:[OCMArg isNotEqual:aValue]])
     OCMStub([mock someMethod:[OCMArg isKindOfClass:[SomeClass class]]])
     OCMStub([mock someMethod:[OCMArg checkWithSelector:aSelector onObject:anObject]])
     OCMStub([mock someMethod:[OCMArg checkWithBlock:^BOOL(id value) { /* return YES if value is ok */ }]])
    
    非对象类型,只能检测第一种情况了,即使指定值通过验证.而对象类型,可以有多种检测方式,还可以通过自己block或函数来校验参数值.

5. mock类方法

  1. Stubbing class methods
     id classMock = OCMClassMock([SomeClass class]);
     OCMStub([classMock aClassMethod]).andReturn(@"Test string");
     
     // result is @"Test string"
     NSString *result = [SomeClass aClassMethod];
    
    mock类方法时,调用OCMClassMock,在这个函数中,动态创建了一个新的meta class,然后将SomeClass的指针指向新的meta class,这样做到 调用类方法时,被OCMock监控到.这也是mock类方法与partial mock对象的相似的地方,对原对象的调用,也会被stub方法截获.但是这里调用的时候就与其他mock对象不同了,是直接调用[SomeClass aClassMethod].
    注意 :如果一个类方法的mock对象stub了一个类方法,而mock对象没有正确释放,那这个方法的stub将一直持续下去.如果对于一个类同时存在多个mock对象,且stub了多个方法,会产生不可预知的后果. 使用OC的语言动态性来替换meta class,产生的一些危险.
  2. 验证方法是否调用
     OCMVerify([classMock aClassMethod]);
    
    调用mock函数,与执行校验,两种形式是不同的,要注意.
  3. 消除二义性
    有些情况下,类中会有同名的类方法和实例方法,这个时候要进行区分,使用ClassMethod
     OCMStub(ClassMethod([(MC *)mock fun])).andDo(^(NSInvocation *invocation){
         NSLog(@"BLOCK BLOCK ");
     });
     OCMStub([(MC *)mock fun]).andDo(^(NSInvocation *invocation){
         NSLog(@"block block ");
     });
     [MC fun];
     [(MC *)mock fun];
    
    这里使用[(MC *)mock fun]的原因是,Xcode区分不了要执行哪个函数,所以报错Multiple methods named 'fun' found with mismatched result,parameter type or attributes.
  4. 将mock的class恢复
     id classMock = OCMClassMock([SomeClass class]);
     
     /* do stuff */
     
     [classMock stopMocking];
    
    因为前面说到了,在mock类方法时,是直接替换类的meta class的,所以,要在测试完毕后,手动将类的meta class替换回去.调用stopMocking,将类恢复原样,mock对象在自己的dealloc中也会自动执行stopMocking的.

6. partial mocks

对象mock和类方法mock有很多相似的地方,因为原理相似,所以处理上也相似.
  1. stubbing methods
     id partialMock = OCMPartialMock(anObject);
     OCMStub([partialMock someMethod]).andReturn(@"Test string");
     
     // result1 is @"Test string"
     NSString *result1 = [partialMock someMethod];
     
     // result2 is @"Test string", too!
     NSString *result2 = [anObject someMethod];
    
    特点就是,对mock对象调用方法,和对原始对象调用方法,效果相同.而其实现原理,是创建一个子类,并将mock对象的类指向这个新生成的类.
  2. 验证调用
     // 验证方法调用时,也只有一种方法.
     OCMVerify([partialMock someMethod]);
    
  3. 将mock的对象恢复
     id partialMock = OCMPartialMock(anObject);
     
     /* do stuff */
     
     [partialMock stopMocking];
    
    将这个对象所对象的class恢复成正常的class.mock对象在自己的dealloc中也会自动执行stopMocking的.

7. 严格mock和预测

预测只是在预测函数是否调用.
  1. 预测函数是否调用
     id classMock = OCMClassMock([SomeClass class]);
     OCMExpect([classMock someMethodWithArgument:[OCMArg isNotNil]]);
     
     /* run code under test, which is assumed to call someMethod */
     
     OCMVerifyAll(classMock)
    
    OCMVerify是验证一条规则,OCMVerifyAll是验证所有规则.
  2. 严格的mock
     id classMock = OCMStrictClassMock([SomeClass class]);
     [classMock someMethod]; // this will throw an exception
    
    如果调用了没有mock的方法,测试就会失败.
  3. 预测返回值
     id classMock = OCMStrictClassMock([SomeClass class]);
     OCMExpect([classMock someMethod]).andReturn(@"a string for testing");
     
     /* run code under test, which is assumed to call someMethod */
     
     OCMVerifyAll(classMock)
    
    可以使用andReturnandThrow等来构建响应链.但事实上这种写法,还不如直接使用OCMVerify.
  4. 延迟预测
     OCMVerifyAllWithDelay(mock, aDelay);
    
    延迟一段时间进行预测.
  5. 设置一个预测序列
     id mock = OCMStrictClassMock([SomeClass class]);
     [mock setExpectationOrderMatters:YES];
     OCMExpect([mock someMethod]);
     OCMExpect([mock anotherMethod]);
     
     // calling anotherMethod before someMethod will cause an exception to be thrown
     [mock anotherMethod];
    
    设置一个预测序列,必须按照序列进行函数调用,否则测试失败.

8. Observer mocks

// 创建一个observerMock
id observerMock = OCMObserverMock();
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
// 监听xxnotification 
[center addMockObserver:observerMock name:@"xxnotification" object:nil];
// 预测将要接受xxnotification
[[observerMock expect] notificationWithName:@"xxnotification" object:[OCMArg any]];
// 某个地方发送了消息
[center postNotificationName:@"xxnotification" object:nil];
// 预测完成且成功
OCMVerifyAll(observerMock);
预测mock对象将要收到SomeNotification消息,

9. 进阶话题

  1. 快速失败
    在严格的mock中,如果调用了没有mock的方法,就会失败.而正常的mock也可以设置这种快速失败的效果,通过OCMReject :
     id mock = OCMClassMock([SomeClass class]);
     OCMReject([mock someMethod]);
    
  2. 重新抛出Exception // 不只所云
    一个异常的抛出,不会导致测试失败.这会发生在方法调用没有在test函数内结束.
  3. Stub创建对象的方法
    OCMock可以stub方法返回对象.而且也会自动的调节返回对象的引用计数,所以可以放心的使用alloc,new,copymultableCopy方法.
     id classMock = OCMClassMock([SomeClass class]);
     OCMStub([classMock new])).andReturn(myObject);
    
    这里使用new,因为想要stub主 init方法是不可能的,这个方法已经被mock实现了,再替换会导致冲突.
  4. method swizzling
     id partialMock = OCMPartialMock(anObject);
     OCMStub([partialMock someMethod]).andCall(differentObject, @selector(differentMethod));
    
    andCall的方法就像是在method swizzling一样.但需要注意的是,partialMock中使用的是object_setClass来替换类,然后才在mockedClass中进行method swizzling.

10. 限制

  1. 在同一时间,只能有一个mock对象来stub一个类的类方法
     // don't do this
     id mock1 = OCMClassMock([SomeClass class]);
     OCMStub([mock1 aClassMethod]);
     id mock2 = OCMClassMock([SomeClass class]);
     OCMStub([mock2 anotherClassMethod]);
    
  2. 如果stub过了方法,就不能再用OCMExpect来预测了
     id mock = OCMStrictClassMock([SomeClass class]);
     OCMStub([mock someMethod]).andReturn(@"a string");
     OCMExpect([mock someMethod]);
     
     /* run code under test */
     
     OCMVerifyAll(mock); // will complain that someMethod has not been called
    
    因为这个OCMExpect本身就是在创建stub了,可以直接在OCMExpect之后直接链式接上andReturn等方法.
  3. 不能对一些特殊的类进行partial mock
     id partialMockForString = OCMPartialMock(@"Foo"); // will throw an exception
     
     NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
     id partialMockForDate = OCMPartialMock(date); // will throw on some architectures
    
    首先不能对toll-free bridged的类进行partial mock,toll-free bridged指那些CoreFoundation,CoreGraphics等框架中可以与OC对象互相转换的类,如NSStringCFString. 以及 objects represented with tagged pointers,如某些架构上的NSDate.
  4. 一些方法不能被stub和verify
     id partialMockForString = OCMPartialMock(anObject);
     OCMStub([partialMock class]).andReturn(someOtherClass); // will not work

评论

此博客中的热门博文

Resolving errSecInternalComponent errors during code signing

原文链接 One code signing issue I commonly see, both here on DevForums and in my Day Job™ with DTS, is that the codesign command fails with errSecInternalComponent. This issue crops up in a wide variety of circumstances and the correct fix depends on the specific problem. This post is my attempt to clarify the potential causes of this error and help folks resolve it. If you have any questions or comments about this, please start a new thread, tagging it with Code Signing so that I see it. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Resolving errSecInternalComponent errors during code signing In some circumstances the codesign command might fail with the error errSecInternalComponent. For example: % codesign -s "Apple Development" "MyTrue" MyTrue: errSecInternalComponent This typically affects folks who are signing code in a nonstandard environm...

iOS:检测使用VPN或Proxy

参考链接: https://www.jianshu.com/p/c3b950dbf86a https://gist.github.com/PramodJoshi/4faad4c91f7dcb4eb9b06be8390c01db http://noodlecode.net/2018/04/check-if-ios-app-is-connected-to-vpn 第一种方法 需要导入框架CFNetwork 然后,这个方法是mrc的:需要添加-fno-objc-arc的flag 代码如下: + ( BOOL )getProxyStatus { NSDictionary *proxySettings = NSMakeCollectable ([( NSDictionary *) CFNetworkCopySystemProxySettings () autorelease]); NSArray *proxies = NSMakeCollectable ([( NSArray *) CFNetworkCopyProxiesForURL (( CFURLRef )[ NSURL URLWithString: @"http://www.google.com" ], ( CFDictionaryRef )proxySettings) autorelease]); NSDictionary *settings = [proxies objectAtIndex: 0 ]; NSLog ( @"host=%@" , [settings objectForKey:( NSString *)kCFProxyHostNameKey]); NSLog ( @"port=%@" , [settings objectForKey:( NSString *)kCFProxyPortNumberKey]); NSLog ( @"type=%@" , [settings objectForKey:( NSString *)kCFProxyTypeKey]); if ([[settings object...

去广告DNS设置,国内ADGuard DNS方案,手机电脑iOS去广告,保护隐私

 原文链接 之前分享过使用mac系统搭建adguard home,这几个月用下来零零散散基本上也被弃用了。主要原因是因为需要保持电脑一直开机。但是我的电脑是笔记本,存在移动各个地域的情况,也就是说只能够屏蔽电脑自身,对于手机而言不太现实。今天偶然发现dnspod推出了高级版的公共解析。dnspod背靠腾讯云,肯定是合法合规的公共解析服务,这个高级版用起来不错。 国内自己搭建解析服务是违法行为,所以这也是为什么使用dnspod的原因。 后台截图 开始使用 首先我们先进入dnspod的公共解析页面,点击开始使用。 专业版公共解析 dnspod会提供几种预设,我们选择「开发者」即可 开发者 然后你就成功的申请到自己个人使用的dns了! 更新拦截规则 我们可以将常见的广告过滤规则加入到dns中。我们在顶部选项卡中选择「拦截规则」。 拦截规则设置 打开adguard adguard 绑定iOS设备 推荐使用描述文件的方式,删除配置时删除描述文件即可。 描述文件 绑定macOS 推荐使用描述文件的方式,删除配置时删除描述文件即可。 描述文件 mac需要在「系统偏好设置」的「网络」中查看是否正在运行。 代理 如果没有运行需要点击「···」来启动服务。 启动服务 绑定路由器 找到自己路由器的DHCP设置,修改dns,然后记得绑定自己的ip。 修改dns 绑定ip 费用 目前有300万次/月的免费额度,但没有超出之后的价格。300万次一个人比较难用完,可以放心使用。 我个人使用iOS设备两台、智能家居、电脑两台,日均请求数大致2万/日。 判断是否搭建成功 可以通过查看日志的方式,日志大概有半小时到一小时的延迟,请耐心等待。