一、OCMock简介
1.1、Mock先容
作为一个动词,mock是模仿、模仿的意思;作为一个名词,mock是可以或许模仿真实对象举动的模仿对象。在软件测试中,mock所模仿的对象是什么呢?它肯定不是我们所测试的对象,而是 SUT(Software Under Test:测试的对象) 的依赖(dependency)。换句话说,mock 的作用是模仿 SUT 依赖对象的举动。
笔墨不好明白,我们画个图,如下图所示,被测试对象是 A,A 依赖的是B,B 依赖的是 C。而我们要 mock 的是 B 的举动。图中 A 就是 SUT。
1.2、OCMock先容
OCMock是一个用于为iOS或Mac OS X项目设置Mock测试的开源项目。
着实现头脑就是根据要mock的对象的class来创建一个对应的对象,而且设置好该对象的属性和调用预定方法后的动作(例如返回一个值,调用代码块,发送消息等等),然后将其记载到一个数组中,接下来开发者自动调用该方法,最后做一个verify(验证),从而判断该方法是否被调用,大概调用过程中是否抛出非常等。
着实就是可以把它当做我们伪造的一个对象,我们给它一些预设的值之类的,然后就可以举行对应的验证了。
1.3、OCMock集成
项目集成 OCMock 第三方库,这个使用 pod 工具直接安装OCMock框架即可。若使用 iBiu 工具安装 OCMock 库需在 podfile 文件同级创建 Podfile.custom。
使用平凡的 pod 文件雷同格式添加 OCmock 如下:
source 'https://github.com/CocoaPods/Specs.git'pod 'OCMock'二、OCMock的入门
关于为什么必要 mock,OCMock 官网的 Introduction 举了以下一个例子(是个标准的 TDD 开发流程,值得学习一下):开发者必要开发一个从 Twitter 上拉取数据,然后更新用户界面的模块,怎样应用 TDD 编写该模块的单位测试。接下来的内容,是根据 TDD 流程分别末节,关于 mock 的存在代价则分散在每个末节各处。
点击下载Demo:ZJHUnitTestDemo
2.1、示例模块分别
起首,分别大抵模块,例如最简单的 MVC 模块分别方式,以确定接口。
Controller
@interface ZJHTwitterViewController : UIViewController@property (nonatomic, strong) ZJHTwitterConnection *connection;@property (nonatomic, strong) ZJHTweetView *tweetView;- (void)updateTweetView;@endData Source
@interface ZJHTwitterConnection : NSObject// 检索新推文的方法。它返回一个ZJHTweetModel对象数组,如果无法处理处罚哀求,则返回nil。- (NSArray <ZJHTweetModel *> *)fetchTweets;@endView
@interface ZJHTweetView : UIView// 一个将单个推文添加到视图的方法- (void)addTweet ZJHTweetModel *)aTweet;@en2.2、确定测试用例三要素
选定实现ZJHTwitterViewController的updateTweetView方法,该方法通过调用connection成员的fetchTweets获取 Twitter 数据,然后调用tweetView成员的addTweet:将数据表现到界面。TDD(Test Driven Development:测试驱动开发) 是测试先行,因此先编写针对updateTweetView方法的单位测试。在此之前,必要思量如那里理Controller对View和Connection的依赖。试想,如果选择直接构建View和Connection的实例,则开发者碰面对以下问题(联合 F.I.R.S.T 原则思量),紧张来自于Connection:
- 使用真实的网络毗连一定大大增加单位测试的运行时长,会违背 Fast 原则;
- Twitter大概在任何时间点返回任何数据,如许碰面对两种都很差的选择:
- 1、在单个单位测试中处理处罚各种相应情况,如许会使单位测试逻辑流程依赖于 Twitter 的具体相应数据,违背了 Isolated 原则;
- 2、针对差异的相应数据编写差异的测试用例,但如许不能包管全部用例的断言都被实行到,而且差异的相应会实行到差异的断言,如许违背了 Repeatly 原则;
- Twitter一样寻常不会返回错误,如 404、500,而且也很难控制 Twitter 返回特定的错误,同时也违背了 Self-verifying 原则;
F.I.R.S.T 原则(参考精良测试实践原则):Fast — 测试应该可以或许被常常实行;Isolated — 测试自己不能依赖于外部因素或其他测试的效果;Repeatable — 每次运行测试都应该产生雷同的效果;Self-verifying — 测试应该依赖于断言,不必要人为干预;Timely — 测试应该和生产代码一同誊写因此,在updateTweetView单位测试中直接构建所依赖的View和Connection的实例黑白常不明智的选择。于是 mock 便应运而生。Mock 是用于在模块的单位测试中,模仿 模块所依赖的对象的特定举动或特定数据的 替人。例如:可以指定 mock 对象的方法返回固定的目标数据(stubbing)、可以校验 mock 对象的方法是否有被触发(verifying)等等。Mock 可以使依赖的举动具备可确定、可编辑、可追踪特性。
回到刚才的例子,由于不必要等候网络数据同步返回,而是直接由 mock 返回模仿数据,因此符合 Fast 原则;别的返回模仿数据高度可控,使之符合 Isolated、Repeatly、Self-verifying 原则。
既然有这么精良的选择,那就可以正式动手编写测试用例了。接下来编写测试用例:Connection从 Twitter 拉取数据乐成后,若Controller调用updateTweetView,View是否有革新数据。起首必要明白单位测试用例的三个根本因素:
given:Connection的fetchTweet方法指定能返回 Twitter 数据;
when:Controller实例调用了updateTweetView;
then:View是否有调用addTweet方法将 Twitter 数据表现到界面;
2.3、编写测试用例
由于测试的目标模块是Controller因此必要构建真实的实例,而依赖Connection和TweetView则只需构建其 mock 替人,并为Controller所持有,此时Controller是不知道它们只是 mock 对象。由于 mock Connection 的 fetchTweets操纵的时间、数据不可确定性,以是必要给 fetchTweets 打桩(stub)返回固定的 Twitter 数据。当Controller实例调用updateTweetView方法时,必要验证(verify)mock TweetView的addTweets:表现 Twitter 数据到界面的操纵被触发。
@implementation ZJHTwitterViewControllerTests- (void)testExample { //--------- Given Start ---------// // 1. 构建Controller实例 ZJHTwitterViewController *controller = [ZJHTwitterViewController new]; // 2. Mock一个ZJHTwitterConnection实例 id mockConnection = OCMClassMock([ZJHTwitterConnection class]); controller.connection = mockConnection; // 创建一些数据 ZJHTweetModel *testTweet1 = [ZJHTweetModel new]; ZJHTweetModel *testTweet2 = [ZJHTweetModel new]; NSArray *tweetArray = @[testTweet1, testTweet2]; // 4. stub Connection 的 fetchTweets 方法使之固定返回Tweet模子数组 OCMStub([mockConnection fetchTweets]).andReturn(tweetArray); // 4. Mock一个TweetView实例 id mockView = OCMClassMock([ZJHTweetView class]); controller.tweetView = mockView; //--------- When Start ---------// // 5. 调用测试目标方法updateTweetView。内里会调用fetchTweets,然后会得到我们存根的数组tweetArray [controller updateTweetView]; //--------- Then Start ---------// // 6. 验证 mock TweetView 的 addTweet: 表现Tweet到界面的操纵被触发 OCMVerify([mockView addTweet:[OCMArg any]]);}@end2.4、编写实今世码
完成了updateTweetView方法的测试用例,就可以大抵清晰updateTweetView必要处理处罚什么数据(stub)、必要调用依赖的哪些方法(verify)。此时运行该测试用例一定不通过,由于还未实现updateTweetView。接下来开始实现updateTweetView。具体代码如下:
@implementation ZJHTwitterViewController- (void)updateTweetView { NSArray *tweets = [self.connection fetchTweets]; if (tweets != nil) { // 展示数据 for (ZJHTweetModel *item in tweets) { [self.tweetView addTweet:item]; } } else { // 处理处罚非常情况 }}@end此时运行测试用例,用例通过,由于满足了测试用例中的OCMVerify的条件:当(given)connection固定正常返回 Tweet 数据时,调用updateTweetView时(when),触发了tweetView的addTweet:方法表现 Tweet 数据到界面。
三、OCMock的示例使用
3.1、天生 Mock 对象三种方式的对比
3.1.1、必要测试的代码
通过对Person类的talk方法举行测试举例,此中也涉及Men类以及Animaiton类,以下是三个类的干系源码。
MOPerson类:
@interface MOPerson()@property(nonatomic,strong) MOMen *men;@end@implementation Person- (void)talk NSString *)str { [self.men logstr:str]; [MOAnimaiton logstr:str];}@endMOMen类
@implementation MOMen-(NSString *)logstr NSString *)str { NSLog(@"%@",str); return str;}@endMOAnimaiton类
@implementation MOAnimaiton+ (NSString *)logstr NSString *)str { NSLog(@"%@",str); return str;}@end3.1.2、Nice Mock
NiceMock 创建的 mock 对象在举行方法测试时会优先调用实例方法,若未找到实例方法,会继承调用同名的类方法。因此该方法可以用来天生mock对象去测试类方法也可以测试对象方法。使用场景:Nice mock 是比力友爱的,当一个没有存根的方法被调用时他不会引起一个非常会验证通过。如果你不想自己对许多的方法举行存根,那么使用 nice mock
- (void)testTalkNiceMock { MOPerson *person1 = [MOPerson new]; // 新建person类 id mockA = OCMClassMock([MOMen class]); // mock一个Men对象 person1.men = mockA; [person1 talk "123"]; // person类实行方法 OCMVerify([mockA logstr:[OCMArg any]]); // 验证 logstr 方法有被调用}3.1.3、Strict Mock
使用方式:测试case如下,mockA是Strict Mock天生,要调用testTalkStrictMock方法,则该方法要使用stub举行存根,否则最后的OCMVerifyAll(mockA)就会抛出非常。使用场景:这种方式创建的 mock 对象,如果调用未 stub(stub 代表存根)的方法,会抛出一个非常。这必要包管在 mock 的生命周期中每一个独立调用的方法都是被存根的,这种方法使用比力严格,很少使用。
- (void)testTalkStrictMock { id mockA = OCMStrictClassMock([MOPerson class]); // StrictMock天生mockA OCMStub([mockA talk:[OCMArg any]]); // 使用stub举行存根 [mockA talk "123"]; // 实行talk方法 OCMVerifyAll(mockA); // 验证mock方法有没有被实行}3.1.4、Partial Mock
如许创建的对象在调用方法时:如果方法被 stub,调用 stub 后的方法,如果方法没有被 stub,调用原来的对象的方法,该方法有限定只能 mock 实例对象。使用场景:当调用一个没有被存根的方法时,会调用实际对象的该方法。当不能很好的存根一个类的方法时,该技能黑白常有效的。
- (void)testTalkPartialMock { MOPerson *person1 = [MOPerson new]; MOMen *men = [MOMen new]; id mockA = OCMPartialMock(men); // 如果方法被 stub,调用 stub 后的方法,如果方法没有被 stub,调用原来的对象的方法// OCMStub([mockA logstr:[OCMArg any]]).andReturn(@"456");; person1.men = mockA; [person1 talk "123"]; OCMVerify([mockA logstr:[OCMArg any]]);}3.2、预期的验证
3.2.1、必要测试的代码
@implementation MOOCMockDemo+ (void)handleLoadFinished NSDictionary *)info { MOPerson *person = [MOPerson personWithInfo:info]; if ([person isValid]) { [self handleLoadSuccessWithPerson:person]; [self showError:NO]; } else { [self handleLoadFailWithPerson:person]; [self showError:YES]; }}+ (void)handleLoadSuccessWithPerson MOPerson *)person { // do something}+ (void)handleLoadFailWithPerson MOPerson *)person { // do something}+ (void)showError BOOL)error { // do something}@end3.2.2、验证预期
- (void)testMockExpect { // 新建mock id mock = OCMClassMock([MOOCMockDemo class]); // 预期下列方法次序实行 [mock setExpectationOrderMatters:YES]; // 预期 + 参数验证 OCMExpect([mock handleLoadSuccessWithPerson:[OCMArg checkWithBlock:^BOOL(id obj) { MOPerson *person = (MOPerson *)obj; return [person.name isEqualToString "momo"]; }]]); // 预期方法 OCMExpect([mock showError:NO]); // 预期不实行 OCMReject([mock handleLoadFailWithPerson:[OCMArg any]]); OCMReject([mock showError:YES]).ignoringNonObjectArgs; // 忽视参数 // 实行方法 NSDictionary *info = @{@"name": @"momo"}; [MOOCMockDemo handleLoadFinished:info]; // 断言 OCMVerifyAll(mock); // OCMVerifyAll 会验证前面的渴望是否有效,只要有一个没调用,就会堕落。 OCMVerifyAllWithDelay(mock, 1); // 支持耽误验证 // 克制Mocking [mock stopMocking];}3.3、网络接口的模仿
3.3.1、必要测试的代码
@implementation ZJHOrderListViewController- (void)getListData { // 接口哀求获取网络数据 [ZJHNetworkTool requestUrl "url" param {} completion:^(NSDictionary * _Nonnull respondDic) { self.dataArr = respondDic[@"data"]; [self refreshView]; }];}- (void)refreshView { // 革新页面 NSLog(@"***ZJH refreshView : %@", self.dataArr);}@end3.3.2、网络接口模仿
- (void)testMockNetwork { ZJHOrderListViewController *listVc = [ZJHOrderListViewController new]; id mockManager = OCMClassMock([ZJHNetworkTool class]); // mock哀求方法,并返回特定参数 OCMStub([mockManager requestUrl:[OCMArg any] param:[OCMArg any] completion:[OCMArg any]]).andDo(^(NSInvocation *invocation){ void (^successBlock)(NSDictionary *respondDic) = nil; [invocation getArgument:&successBlock atIndex:4]; successBlock( @{ @"data" : @[@"a", @"b", @"c"] } ); }); [listVc getListData]; OCMVerifyAll(mockManager);}以上就是在调用 getListData 方法内部调用了接口,该方法就可以在调用接口后模仿必要的返回数据,successBlock 中的就是返回的测试数据。本方式是通过获取接口调用的方法署名,获取 successBlock 乐成回调传参并手动调用。同样可以模仿接口失败的情况,只需获取到署名中的对应的失败回调就可以实现了。使用场景:誊写单位测试方法时涉及网络接口的模仿,通过该方式 mock 接口返回效果。
四、OCMock根本API详解
本章根据官方文档 Documentation 改编而来,可以在里查察怎样具体使用OCMock。
4.1、创建模仿对象 Creating mock objects
4.1.1、模仿实例 Class mocks
// 根据类,模仿着实例id mockPerson = OCMClassMock([MOPerson class]);4.1.2、模仿署理 Protocol mocks
// 根据协议名,模仿已经实现协议的实例id mockProtocol = OCMProtocolMock(@protocol(MOTitleLineViewDelegate));// 然后mock协议方法4.1.3、严格模仿类和协议 Strict class and protocol mocks
// 在收到没有预期(expect)的方法时引发非常id strictMockClass = OCMStrictClassMock([MOPerson class]);id strictMockProtocol = OCMStrictProtocolMock(@protocol(MOTitleLineViewDelegate));4.1.4、部分模仿 Partial mocks
这里先容一个界说:Stub,存根,就是模仿一个函数。
MOPerson *aPerson = [[MOPerson alloc] init];id partialMockPerson = OCMPartialMock(aPerson);调用一个函数:已经存根的就触发存根的(Stub);未存根的就触发原有实例的(aPerson)。
4.1.5、观察者模仿 Observer mocks
用官方的XCTNSNotificationExpectation
4.2、存根方法 Stubbing methods
4.2.1、模仿方法的返回值 Stubbing methods that return objects
OCMStub([partialMockPerson name]).andReturn(@"moxiaoyan"); OCMStub([mock aMethodReturningABoolean]).andReturn(YES);4.2.2、委托给另一个方法 Stubbing methods that return values
MOPerson *anotherPerson = [[MOPerson alloc] init];// 另一个对象的方法,方法署名必要划一OCMStub([partialMockPerson name]).andCall(anotherPerson, @selector(name));4.2.3、委托给一个block Delegating to another method
OCMStub([partialMockPerson name]).andDo(^(NSInvocation *invocation){ // 调用name方法时,将会调用这个block // invocation会携带方法参数 // invocation可以设置返回值});OCMStub([partialMock name]).andDo(nil);4.2.4、委托给块 Delegating to a block
模仿对象将在调用函数时,调用该Block。该Block可以从调用的对象中读取参数,并可以设置返回值。
OCMStub([mock someMethod]).andDo(^(NSInvocation *invocation) { /* block that handles the method invocation */});4.2.5、模仿 通过参数返回值的方法 的返回值 Returning values in pass-by-reference arguments
4.2.5.1、对象参数
通过参数传回值:
// 模仿 应该返回的参数值NSError *error = [NSError errorWithDomain "获取friends失败(stubbed)" code:001 userInfo:nil];OCMStub([partialMockPerson loadFriendsWithError:[OCMArg setTo:error]]);// 函数调用,得到模仿的值NSError *resultError = nil;[partialMockPerson loadFriendsWithError:&resultError];NSLog(@"%@", resultError); // 001, 获取friends失败(stubbed)4.2.5.2、非对象参数
OCMStub([mock someMethodWithReferenceArgument:[OCMArg setToValue:OCMOCK_VALUE((int){aValue})]]);4.2.6、模仿block参数 Invoking block arguments
// invokeBlock默认模仿,参数都为默认值OCMStub([partialMockPerson deviceWithComplete:[OCMArg invokeBlock]]);[partialMockPerson deviceWithComplete:^(NSString * _Nonnull value) { NSLog(@"%@", value); // nil}];// invokeBlockWithArgs模仿,可以设置参数值OCMStub([partialMockPerson deviceWithComplete:[OCMArg invokeBlockWithArgs "iPhone"]]);[partialMockPerson deviceWithComplete:^(NSString * _Nonnull value) { NSLog(@"%@", value); // iPhone}];4.2.7、抛出非常 Throwing exceptions
设置函数被调用时,抛出非常:
NSException *exception = [[NSException alloc] initWithName "获取name非常" reason "name为空" userInfo:nil];OCMStub([partialMockPerson name]).andThrow(exception);4.2.8、发出关照 Posting notifications
设置函数被调用是,发出关照(notify)
NSNotification *notify = [NSNotification notificationWithName:@"关照" object:self userInfo:nil];OCMStub([partialMockPerson name]).andPost(notify);4.2.9、链接模仿方法 Chaining stub actions
诸如andReturn和 之类的全部操纵andPost都可以链接
// 模仿对象将发布关照并返回值OCMStub([mock someMethod]).andPost(aNotification).andReturn(aValue);4.2.10、转发给真正的对象/类 Forwarding to the real object / class
当使用部分模仿实例和模仿类方法时,可以将存根方法转发给真实对象或类。这仅在链接操纵或使用渴望时有效。
OCMStub([partialMockPerson name]).andForwardToRealObject();4.2.11、什么也不做 Doing nothing
可以将nil而不是块通报给andDo。这仅在部分模仿或模仿类方法时有效。在这些情况下,使用andDo(nil)有效地克制了现有类中的举动。
OCMStub([mock someMethod]).andDo(nil);4.2.12、满足XCTest的渴望(必要OCMock3.8)Fulfilling XCTest expectations
当调用该方法时,XCTest 框架中的渴望得到满足:
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"XCTest的渴望"];OCMStub([partialMockPerson name]).andFulfill(expectation);4.2.13、记载消息(必要OCMock3.8)Logging messages
OCMStub([partialMockPerson name]).andLog(@"%@", @"hehe");调用该方法时,format通过NSLog。很大概您想在一个链中使用它,大概后跟andReturn()或andForwardToRealObject()
4.2.14、打开调试,断点会生效(必要OCMock3.8)
OCMStub([partialMockPerson name]).andBreak();当调用该方法时,调试器被打开,就似乎一个断点被掷中一样。堆栈将在 OCMock 的实现中的某个地方竣事,但是如果您进一步查察,越过__forwarding__帧,您应该可以或许看到您的代码调用该方法的位置。
4.3、验证交互 Verifying interactions
4.3.1、验证方法已调用 Verify-after-running
[aPerson name];OCMVerify([partialMockPerson name]);验证name已被测试代码调用。如果尚未调用该方法,则会陈诉错误。
4.3.2、验证Stubbed的方法被调用 Stubs and verification
OCMStub([partialMockPerson name]).andReturn(@"momo");[aPerson name];OCMVerify([partialMockPerson name]);可以存根一个方法并仍然验证它是否已被调用。
4.3.3、量词要求 Quantifiers requires
验证方法被调用的次数:
OCMVerify(atLeast(2), [partialMockPerson name]);OCMVerify(never(), [partialMock doStuff]);OCMVerify(times(0), [partialMock doStuff]);OCMVerify(times(n), [partialMock doStuff]);OCMVerify(atLeast(n), [partialMock doStuff]);OCMVerify(atMost(n), [partialMock doStuff]);4.4、参数束缚 Argument constraints
4.4.1、任何束缚 The any constraint
// stub方法,可以相应任何调用OCMStub([partialMockPerson addChilden:[OCMArg any]]); // 参数是任何对象OCMStub([partialMockPerson takeMoney:[OCMArg anyPointer]]); // 参数是任何指针OCMStub([partialMockPerson changeWithSelector:[OCMArg anySelector]]); // 参数是任何选择子4.4.2、忽视没有对象参数 Ignoring non-object arguments
stub方法,可以相应非对象参数的调用(可以相应参数没有通过的调用:无论是对象参数 or 非对象参数)
OCMStub([partialMockPerson setAge:0]).ignoringNonObjectArgs();4.4.3、匹配参数 Matching arguments
stub方法,仅相应匹配的参数的调用
MOPerson *bPerson = [[MOPerson alloc] init];OCMStub([partialMockPerson addChilden:bPerson]);OCMStub([partialMockPerson addChilden:[OCMArg isNil]]);OCMStub([partialMockPerson addChilden:[OCMArg isNotNil]]);OCMStub([partialMockPerson addChilden:[OCMArg isNotEqual:bPerson]]);OCMStub([partialMockPerson addChilden:[OCMArg isKindOfClass:[MOPerson class]]]);会触发 anObject 的 aSelector 方法,并将参数传入
在该方法中判断参数是否通过,通过就:返回YES, 否则:返回NO
id anObject = nil;SEL aSelector = @selector(addChilden ;OCMStub([partialMockPerson addChilden:[OCMArg checkWithSelector:aSelector onObject:anObject]]);OCMStub([partialMockPerson addChilden:[OCMArg checkWithBlock:^BOOL(id value) { // 判断参数是否通过,通过就:返回YES, 否则:返回NO return YES;}]]);4.4.4、使用Hamcrest匹配
OCMStub([partialMockPerson addChilden:startsWith(@"foo")]);4.5、模仿类方法 Mocking class methods
4.5.1、存根类方法 Stubbing class methods
id mockPerson = OCMClassMock([MOPerson class]);OCMStub([mockPerson mo_className]).andReturn(@"XXMOPerson");4.5.2、消除类和实例方法的歧义 Disambiguating class and instance methods
// (1)此时如果没有同名的实例方法,mo_className类方法是可以被精确Stub的NSString *className1 = [MOPerson mo_className]; // XXMOPerson// (2)但是如果实例方法有跟之同名时:NSString *instanceName = [mockPerson mo_className]; // XXMOPersonNSString *className2 = [MOPerson mo_className]; // class MOPerson// 则必要用一下方法举行StubOCMStub(ClassMethod([mockPerson mo_className])).andReturn(@"MOMOPerson");NSString *className3 = [MOPerson mo_className]; // XXMOPerson4.5.3、验证类方法已实行 Verifying invocations of class methods
[mockPerson mo_className];OCMVerify([mockPerson mo_className]);4.5.4、规复类 Disambiguating class and instance methods
[mockPerson stopMocking];4.6、部分模仿 Partial mocks
4.6.1、存根方法 Stubbing methods
id partialMockPerson = OCMPartialMock(aPerson);OCMStub([partialMockPerson mo_className]).andReturn(@" artail Class");NSString *partialName = [partialMockPerson mo_className]; // Partail ClassNSString *personName = [aPerson mo_className]; // Partail Class4.6.2、验证调用 Verifying invocations
[partialMockPerson mo_className];OCMVerify([partialMockPerson mo_className]);4.6.3、规复对象 Restoring the object
[partialMockPerson stopMocking];4.7、严格的模仿和渴望 Strict mocks and expectations
4.7.1、设置渴望-运行-验证 Expect-run-verify
id mockPerson = OCMClassMock([MOPerson class]);OCMExpect([mockPerson addChilden:[OCMArg isNotNil]]);[mockPerson addChilden:[MOPerson new]]; // 只要有一次不为nil,就通过了验证![mockPerson addChilden:nil];OCMVerifyAll(mockPerson);4.7.2、严格的模仿和快速失败 Strict mocks and failing fast
id strictPerson = OCMStrictClassMock([MOPerson class]);[strictPerson mo_className]; // 没有渴望该方法的调用,以是会测试失败4.7.3、存根和渴望 Stub actions and expect
也可以在渴望的情况下使用andReturn、andThrow等。这将在调用方法时运行存根操纵,并在验证时确保该方法被实际调用
OCMExpect([strictPerson mo_className]).andReturn(@"instance_MOPerson");OCMExpect([strictPerson mo_className]).andThrow([NSException ...]);[strictPerson mo_className];OCMVerifyAll(strictPerson);4.7.4、耽误验证 Verify with delay
OCMExpect([strictPerson mo_className]);[strictPerson mo_className];OCMVerifyAllWithDelay(strictPerson, 4.0); // NSTimeInterval, 通常会在满足预期后立即返回4.7.5、按次序验证 Verifying in order
一旦调用了不在“预期列表”中的下一个方法,模仿就会快速失败并抛出非常。
[strictPerson setExpectationOrderMatters:YES];OCMExpect([strictPerson mo_className]);OCMExpect([strictPerson addChilden:[OCMArg any]]);// 调用次序错了,测试就会失败[strictPerson mo_className];[strictPerson addChilden:nil];4.8、观察者模仿 Observer mocks
从OCMock 4.8开始不保举使用观察者模仿。请改用XCTNSNotificationExpectation
4.9、进阶主题 Advanced topics
4.9.1、快速失败的通例模仿 (必要OCMock3.3) Failing fast for regular (nice) mocks
strict模仿:调用未存根的方法会抛出非常
通例模仿:只是返回默认值;可以为函数设置快速失败:
id mockPerson = OCMClassMock([MOPerson class]);OCMReject([mockPerson mo_className]);在这种情况下,模仿将担当全部方法,除了mo_className,如果调用该函数,则将引发非常。
4.9.2、重新验证失败后快速抛出非常 Re-throwing fail fast exceptions in verify all
在快速失败模式下,非常大概不会导致测试失败(如:当方法的调用堆栈未在测试中竣事时)
OCMerifyAll调用时,快速失败非常将重新引发,可以确保检测到来自关照等不必要的调用
4.9.3、存根创建对象的方法 Stubbing methods that create objects
MOPerson *myPerson = [[MOPerson alloc] init];OCMStub([mockPerson copy]).andReturn(myPerson);会根据方法名,自动返回对象的:alloc、new、copy、mutableCopy (引用计数)
注意:init方法无法Stub,由于该方法是由模仿自己实现的。 当init方法再次被调用时,会直接返回模仿对象self
如许就可以有效的对alloc、init举行Stub
4.9.4、基于实现的方法交换 Instance-based method swizzling
MOPerson *person = [[MOPerson alloc] init];id partialMockPerson = OCMPartialMock(person);OCMStub([partialMockPerson mo_className]).andCall(myPerson, @selector(name));方法的名称可以差异,但是署名应该雷同
4.9.5、冲破保留周期 Breaking retain cycles
[mockPerson stopMocking];[partialMockPerson stopMocking];4.9.6、禁用短语法 Disabling short syntax
禁用 没有前缀的宏:ClassMethod()、atLeast()、…
用有前缀的宏:OCMClassMethod()、OCMAtLeast()、…
4.9.7、克制为特定类创建模仿 (必要OCMock3.8) Stopping creation of mocks for specific classes
一些框架在运行时动态更改对象的类。OCMock如许做是为了实现部分模仿,而且Foundation框架将更改类作为(KVO)机制的一部分。
如果不细致和谐,大概会导致不测举动或crash。
OCMock知道KVO,并小心克制与之发生辩论
对于别的框架,OCMock仅提供了一种选择退出模仿以免发生不测举动的机制
+ (BOOL)supportsMocking NSString **)reason { *reason = @"Don't want to be mocked." return NO;}通过实现上面的方法,一个类可以选择不被Mock。当开发职员实验为此类创建模仿步伐时,将引发非常,表明问题说在该方法在单独调用中返回差异的值是可以担当的,这使它在运行时对特定条件做出反应。如果该方法为reason赋值,返回值将被忽略。对于全部未实现此方法的类,OCMock假定可以担当Mock
4.9.8、查抄部分Mock (必要OCMock3.8) Checking for partial mock
判断是否 是部分模仿对象
BOOL isPartialMockObj = OCMIsSubclassOfMockClass(objc_getClass(partialMockPerson));4.10、范围性 Limitations
4.10.1、一次只能有一个Mock可以在给定类上存根方法
不要如许做:
id mock1 = OCMClassMock([SomeClass class]);OCMStub([mock1 aClassMethod]);id mock2 = OCMClassMock([SomeClass class]);OCMStub([mock2 anotherClassMethod]);如果添加了存根类方法的模仿对象未开释,则存根方法将一连存在,纵然在测试中也是如此。如果多个模仿对象同时操纵同一类,则举动将不可推测。
4.10.2、渴望Stub方法无效
id mock = OCMStrictClassMock([SomeClass class]);OCMStub([mock someMethod]).andReturn(@"a string");OCMExpect([mock someMethod]);由于当前实现了模仿对象的方法,Stub会处理处罚全部对它的调用。意味着纵然调用了该方法,验证也会失败。克制此问题:
- 方法1:通过andReturn在Expect语句中添加
- 方法2:在设置渴望之后存根
4.10.3、不能为某些特殊类创建部分模仿
id partialMockForString = OCMPartialMock(@"Foo"); // 会抛出非常NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];id partialMockForDate = OCMPartialMock(date); // 会对一些架构造成影响吗无法为 toll-free bridged 类的实例创建局部模仿
无法为 某些实例创建以标志指针表现的对象,如:NSString、在某些体系结构上、NSDate在某些体系结构上
4.10.4、某些方法无法存根或验证
id partialMockForString = OCMPartialMock(anObject);OCMStub([partialMock class]).andReturn(someOtherClass); // will not work无法模仿许多核心运行时方法。包罗:init、class、methodSignatureForSelector:、forwardInvocation:、respondsToSelector等等
4.10.5、NSString和NSArray上的类方法无法存根或验证
// 无法生效、该方法将不会被存根id stringMock = OCMClassMock([NSString class]);// 无法在NSString和NSArray上存根或验证类方法。实验如许做没有任何效果。OCMStub([stringMock stringWithContentsOfFile:[OCMArg any] encoding:NSUTF8StringEncoding error:[OCMArg setTo:nil]]);4.10.6、NSManagedObject的类方法及其子类无法存根或验证
// 无法生效、该方法将不会被存根id mock = OCMClassMock([MyManagedObject class]);// 无法在其NSManagedObject或其子类上存根或验证类方法。实验如许做没有任何效果。OCMStub([mock someClassMethod]).andReturn(nil);4.10.7、无法验证 NSObject 上的方法
id mock = OCMClassMock([NSObject class]);/* run code under test, which calls awakeAfterUsingCoder: */OCMVerify([mock awakeAfterUsingCoder:[OCMArg any]]); // still fails不大概使用在 NSObject 中实现的方法或其上的种别举行运行后验证。
在某些情况下,可以对方法举行存根,然后对其举行验证。
当方法在子类中被覆盖时,可以使用运行后验证
4.10.8、无法验证核心 Apple 类中的私有方法
UIWindow *window = /* get window somehow */id mock = OCMPartialMock(window);/* run code under test, which causes _sendTouchesForEvent: to be invoked */OCMVerify([mock _sendTouchesForEvent:[OCMArg any]]); // still fails不大概在核心 Apple 类中使用私有方法运行后验证。
具体来说,在以 NS 或 UI 作为前缀的类中,全部带有下划线前缀和/或后缀的方法。
在某些情况下,可以对方法举行存根,然后对其举行验证
4.10.9、运行后验证不能使用耽误
现在无法验证具有耽误的方法。这现在只能使用下面在严格模仿和渴望中形貌的expect-run-verify方法。
4.10.10、测试中使用多线程
OCMock 不是完全线程安全的。直到 4.2.x 版本 OCMock 根本不知道线程。来自多个线程的模仿对象上的任何操纵组合都大概导致问题并使测试失败。
从 OCMock 4.3 开始,仍然必要从单个线程调用全部设置和验证操纵,最好是测试运行步伐的主线程。
但是,可以从多个线程使用模仿对象。模仿对象乃至可以在差异的线程中使用,而其设置在主线程中继承举行。
五、部分增补
5.1、单例的mock
不能直接mock单例的,会引起mock辩论。保举的写法:
// 每次mock alloc 一个单例id center = OCMPartialMock([[QLLoginCenter alloc] init]); // mock 它的 sharedInstance 方法OCMStub([[center classMethod] sharedInstance]).andReturn(center);
参考链接:
iOS中的测试:OCMock:https://www.jianshu.com/p/44ea034ac755
iOS_单位测试三之OCMock使用:https://blog.csdn.net/Margaret_MO/article/details/115420007
iOS_单位测试三之OCMockDemo:https://blog.csdn.net/Margaret_MO/article/details/118341525
iOS 单位测试之常用框架 OCMock 详解:https://www.51cto.com/article/707544.html
iOS单位测试-06-OCMoke和Stub详解:https://www.jianshu.com/p/6fd98f95d1ba |