原文链接
官网设置方法
官网设置方法
关于OCMock的学习
在写单元测试时,不可避免的要尽可能少地实例化一些具体的组件,来保持测试既短又快.我们会使用
mock来替代实例化具体的依赖对象.mock是指在测试中用一个伪造的有预定义行为的具体对象的替身对象,而被测试的对象不知道两者的差异.官网点这里
本篇文章书写时,是基于OCMock的3.3版本的API的。
1. 创建Mock对象
- Class mocks
id classMcok = OCMClassMock([SomeClass class]);创建一个mock对象,可以当做SomeClass的实例来使用.可以mock类的实例,也可以mock类以使用类方法,在下面将会提到. - Protocol mocks
id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));类型mock类的对象一样,protocolMock可以像SomeProtocol的具体实现一样进行使用. - Strict class and protocol mocks
id classMock = OCMStrictClassMock([SomeClass class]); id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));创建一个严格的mock对象. mock对象会对将要执行的方法进行一定预测,如果没有设定预测,mock对象一般会返回nil或者返回类型的默认值.但严格的mock对象,规定了如果没有进行预测设定,而调用了这些方法,就会抛出异常,后面还会详细介绍. - Partial mocks
id partialMock = OCMPartialMock(anObject);创建一个anObject的mock对象,对于这个mock对象,如果设置了stub方法,对mock对象调用方法,mock对象会执行这个方法.如果对真实对象调用方法,依旧会执行mock对象的stub方法.而如果没有设置stub的方法,调用时会转发给真实对象. 下面会详解partial mocks - Observer mocks
id observerMock = OCMObserverMock();创建一个可以监听Notifications的mock,mock对象要注册监听的消息.下面会有详细介绍.
2. stubbing methods
stub方法,即对方法进行预测,如设置返回值,设置调用逻辑,抛出异常等等.设置stub后,调用函数时,原函数不再执行,而以stub的逻辑来进行交互.- 返回对象
OCMStub([mock someMethod]).andReturn(anObject);告诉mock对象,在someMethod方法调用时,以固定返回值返回. - 返回非对象类型
OCMStub([mock someMethod]).andReturn(YES);与返回对象类似,区别在于,如果传的值与方法的返回值类型不同,且不是简单可以替换的类型类型的话,如int和long的互换,则会抛出错误, - 执行时调用其他方法
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)); - 调用block
OCMStub([mock someMethod]).andDo(^(NSInvocation *invocation) { /* block that handles the method invocation */ });在方法调用时,将参数以NSInvocation的形式进行封装,传递给block进行一些附加操作.由于传递的是NSInvocation,所以可以在block中来设置调用的返回值. - 设置参数中的返回值
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: mockedset函数没有调用,直接将mocked的值设置给了arguments. 这种返回值在参数中的,一般用在 传递NSError的函数中.对于下面的值的设置,设置的值是基础类型而不是对象,如:- (void)set:(int *)kk { *kk = 12; } OCMStub([partialMock set:[OCMArg setToValue:OCMOCK_VALUE(11)]]); - 执行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]; - 扔出一个异常
OCMStub([mock someMethod]).andThrow(anException); - 发送一个消息
OCMStub([mock someMethod]).andPost(aNotification); - 链式编程
OCMStub([mock someMethod]).andPost(aNotification).andReturn(aValue);这里所有的操作都是可以链式传递的,所以调用函数时,可以同时执行block,发送消息,设定返回值等等. - 消息传递给真实的对象
OCMStub([mock someMethod]).andForwardToRealObject();只有在使用partial mock或者mock 类方法时,才能发消息给真的对象. 一般用在链式编程中,或者使用期望的时候. - 什么也不做
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. 参数约束
参数约束,是指在符合约束的情况下,才会允许调用发送,否则会忽略调用.
- any ,不做约束
OCMStub([mock someMethodWithAnArgument:[OCMArg any]]) OCMStub([mock someMethodWithPointerArgument:[OCMArg anyPointer]]) OCMStub([mock someMethodWithSelectorArgument:[OCMArg anySelector]]) - 非对象类型的忽略
[[[mock stub] ignoringNonObjectArgs] someMethodWithIntArgument:0]这个时候,OCMock就没有提供宏给我们使用了,上面对于对象来说,可以设置一些参数忽略检测,一些参数进行检测,但是这个非对象类型,设置这个ignoringNonObjectArgs后,这个函数所有的非对象类型就全部被忽略了. - 对象类型的检测
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类方法
- 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,产生的一些危险. - 验证方法是否调用
OCMVerify([classMock aClassMethod]);调用mock函数,与执行校验,两种形式是不同的,要注意. - 消除二义性有些情况下,类中会有同名的类方法和实例方法,这个时候要进行区分,使用
ClassMethodOCMStub(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. - 将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有很多相似的地方,因为原理相似,所以处理上也相似.
- 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对象的类指向这个新生成的类. - 验证调用
// 验证方法调用时,也只有一种方法. OCMVerify([partialMock someMethod]); - 将mock的对象恢复
id partialMock = OCMPartialMock(anObject); /* do stuff */ [partialMock stopMocking];将这个对象所对象的class恢复成正常的class.mock对象在自己的dealloc中也会自动执行stopMocking的.
7. 严格mock和预测
预测只是在预测函数是否调用.
- 预测函数是否调用
id classMock = OCMClassMock([SomeClass class]); OCMExpect([classMock someMethodWithArgument:[OCMArg isNotNil]]); /* run code under test, which is assumed to call someMethod */ OCMVerifyAll(classMock)OCMVerify是验证一条规则,OCMVerifyAll是验证所有规则. - 严格的mock
id classMock = OCMStrictClassMock([SomeClass class]); [classMock someMethod]; // this will throw an exception如果调用了没有mock的方法,测试就会失败. - 预测返回值
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)可以使用andReturn和andThrow等来构建响应链.但事实上这种写法,还不如直接使用OCMVerify. - 延迟预测
OCMVerifyAllWithDelay(mock, aDelay);延迟一段时间进行预测. - 设置一个预测序列
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. 进阶话题
- 快速失败在严格的mock中,如果调用了没有mock的方法,就会失败.而正常的mock也可以设置这种快速失败的效果,通过
OCMReject:id mock = OCMClassMock([SomeClass class]); OCMReject([mock someMethod]); - 重新抛出
Exception// 不只所云一个异常的抛出,不会导致测试失败.这会发生在方法调用没有在test函数内结束. - Stub创建对象的方法OCMock可以stub方法返回对象.而且也会自动的调节返回对象的引用计数,所以可以放心的使用
alloc,new,copy和multableCopy方法.id classMock = OCMClassMock([SomeClass class]); OCMStub([classMock new])).andReturn(myObject);这里使用new,因为想要stub主init方法是不可能的,这个方法已经被mock实现了,再替换会导致冲突. - method swizzling
id partialMock = OCMPartialMock(anObject); OCMStub([partialMock someMethod]).andCall(differentObject, @selector(differentMethod));andCall的方法就像是在method swizzling一样.但需要注意的是,partialMock中使用的是object_setClass来替换类,然后才在mockedClass中进行method swizzling.
10. 限制
- 在同一时间,只能有一个mock对象来stub一个类的类方法
// don't do this id mock1 = OCMClassMock([SomeClass class]); OCMStub([mock1 aClassMethod]); id mock2 = OCMClassMock([SomeClass class]); OCMStub([mock2 anotherClassMethod]); - 如果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等方法. - 不能对一些特殊的类进行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对象互相转换的类,如NSString和CFString. 以及objects represented with tagged pointers,如某些架构上的NSDate. - 一些方法不能被stub和verify
id partialMockForString = OCMPartialMock(anObject); OCMStub([partialMock class]).andReturn(someOtherClass); // will not work
评论
发表评论