本文使用的API为旧版OCMockAPI,新版也兼容旧版API。译者在使用旧版本API的地方添加了相应的新版本。版本(OCMock3)API供读者参考。发烧友,本文假定读者熟悉Xcode5的测试框架XCTest,或BBD测试工具Kiwi,或其他iOS测试框架。什么是模拟?几乎是一只纸老虎当我们编写单元测试时,不可避免地要实例化尽可能少的具体组件,以保持测试的简短和快速。并保持单位隔离。在现代面向对象系统中,被测组件很可能有多个依赖对象。我们使用模拟而不是实例化特定的依赖类。模拟是具有被测预定义行为的具体对象的伪造替代对象。被测组件不知道其中的区别!您的组件旨在适应更大的系统,您可以自信地使用模拟来测试您的组件。Commonmockusecasestub方法我们用一个简单的例子来开始解释OCMock中的通用stub语法。idjalopy=[OCMockmockForClass[Carclass]];[[[jalopystub]andReturn:@"75kph"]goFaster:[OCMArgany]units:@"kph"];//ifreturningascalarvalue,andReturnValue:canbeusedOCMock3新版本对应APIidjalopy=OCMStrictClassMock([Carclass]);OCMStub([jalopygoFaster:[OCMArgany]单位:@"kph"]).andReturn(@"75kph");//ifreturningascalarvalue,andReturnValue:canbeused这个简单的例子首先从Car类(Mastercar)模拟一辆老爷车,然后,存根goFaster:方法,以便它返回字符串@”75kph”。存根语法可能看起来有点奇怪,但这是一种常见的做法:ourMockObjectstub]whatItShouldReturn]method:OCMock3新版本对应于APIOCMStub([ourMockObjectmethod:]).andReturn()一个非常重要的注意事项:注意使用[OCMArg任何]。当指定带参数的方法时,如果调用该方法且参数为指定参数,则mock将返回andReturn:指定的值。[OCMArgany]方法告诉存根匹配所有参数值。例如:[cargoFaster:84units:@"mph"];不会触发存根,因为最后一个参数与“kph”不匹配。当mock实例类方法上找不到同名的实例方法时,类方法OCMock会查找同名的方法。在同名的情况下(类方法和实例方法同名),使用classMethod指定类方法:[[[[jalopystub]classMethod]andReturn:@"expired"]checkWarrany];OCMock3中classMethod和instanceMethod的stub方法是一样的,例如:idclassMock=OCMClassMock([SomeClassclass]);OCMStub([classMockaClassMethod]).andReturn(@"Teststring");//resultis@"Teststring"NSString*result=[SomeClassaClassMethod];mock类型——niceMock、partialMockOCMock提供了几种不同类型的mock,每种都有其特定的使用场景。使用此方法创建任何模拟:idmockThing=[OCMockmockForClass[Thingclass]];新版本的OCMock3对应APIidmockThing=OCMStrictClassMock([Thingclass]);这就是我所说的“香草”模拟。'vanilla'mock在调用没有存根的方法时抛出异常。这导致了一个单调的模拟,其中每个方法调用都在模拟的生命周期内被存根。(有关更多信息,请参阅下一节存根。)如果您不想存根很多方法,请使用“nice”模拟。'nice'mock非常有礼貌,在调用非存根方法时不会抛出异常。idniceMockThing=[OCMockniceMockForClass[Thingclass]];新版OCMock3对应APIidniceMockThing=OCMClassMock([Thingclass]);最后一种模拟类型是“部分”模拟。当调用非存根方法时,该方法被转发给真实对象。这是一个模拟技术欺骗,但是当有一些类不适合被很好地存根时非常有用。事物*someThing=[Thingalloc]init];idaMock=[OCMockObjectpartialMockForObject:someThing]OCMock3新版本对应APIThing*someThing=[Thingalloc]init];idaMock=OCMPartialMock(someThing);验证方法是否被调用非常简单。这可以通过期望拒绝和验证方法来完成:idniceMockThing=[OCMockniceMockForClass[Thingclass]];[[niceMockThingexpect]问候:@“你好”];//验证方法是否按预期被调用[niceMockingverify];新版OCMock3对应APIidniceMockThing=OCMClassMock([Thingclass]);OCMVerify([niceMockThinggreeting:@"你好"]);未调用已验证方法时将抛出异常。如果您使用的是XCTest,请使用XCTAssertNotThrow包装验证调用。拒绝方法调用也是一样,但是调用方法时会抛出异常。就像存根一样,过去传递的选择器和参数必须与调用中传递的参数匹配。使用[OCMArgany]可以简化我们的工作。处理块参数OCMock也可以处理块回调参数。块回调通常用于网络代码、数据库代码或任何异步操作。在此示例中,考虑以下方法:-(void)downloadWeatherDataForZip:(NSString*)zipcallback:(void(^)(NSDictionary*response))callback;在这个例子中,我们有一个下载天气zip数据的方法,并将下载的字典委托给一个块回调。在测试中,我们使用预定义的天气数据测试回调处理。测试失败场景也是明智的。你永远不知道网络会返回给你什么!//1.stubusingOCMockandDo:运算符。[[[groupModelMockstub]andDo:^(NSInvocation*invoke){//2.declareablockwithsamesignaturevoid(^weatherStubResponse)(NSDictionary*dict);//3.linkargument3withourblockcallback[invokegetArgument:&weatherStubResponseatIndex:3;//4。invokeblockwithpre-definedinputNSDictionary*testResponse=@{@"high":43,@"low":12};weatherStubResponse(groupMemberMock);}]downloadWeatherDataForZip@"80304"callback:[OCMArgany]];OCMock3新版本对应API//1.stubusingOCMockandDo:operator.OCMStub([groupModelMockdownloadWeatherDataForZip:@"80304"callback:[OCMArgany]]]).andDo(^(NSInvocation*invocation){//2.declareablockwithsamesignaturevoid(^weatherStubResponse)(NSDictionary*dict);//3.linkargument3withwithourblockcallback[invokegetArgument:&weatherStubResponseatIndex:3];//4.invokeblockwithpre-definedinputNSDictionary*testResponse=@{@"high":43,@"low":12};weatherStubResponse(groupMemberMock);});这里的总体思路很简单,即便如此,他的实现需要一些解释:1.这个模拟对象使用带有NSInvocation参数的“andDo”方法。NSInvocation对象表示方法调用的“客观化”(实际上我不知道这是什么鬼东西)的性能。通过这个NSinvocation对象,拦截传递给我们方法的块参数成为可能。2.声明一个与我们测试的方法具有相同方法签名的块参数。3.NSInvocation实例方法“getArgument:atIndex:”将分配的块函数传递给原函数中定义的块函数。注意:在Objective-C中,传递给任何方法的前两个参数是“self”和“_cmd”。这是运行时的一个小特性,也是我们在使用下标获取NSInvocation参数时需要考虑的事情。4.最后,将预定义字典传递给此回调。最后,我希望这篇文章和例子已经说明了OCMock的一些最常见的用途。OCMock站点:http://ocmock.org/features/是学习OCMock的最佳场所。模拟是单调的,但对于现代OO系统来说是必需的。如果依赖图难以用mock进行测试,则表明您的设计需要重新考虑。
