0%

OCMock使用

第一次用还有点费劲
经过两个多月的使用发现还是挺方便的,也学到了不少invocation的东西,但是总感觉测试有点事后诸葛亮的味道,尤其是当前对着已有的代码写测试用例,还没有get到它的威力,还是TDD开发,啥时候试试看。

里程碑文件
Faking network calls for iOS unit tests

使用

网络模拟

这里说下自己开始遇到的一个问题,网络请求可能分装了很多层,如果想要覆盖更多的代码,就要让接口请求尽量在更外层,真正mock替换的方法尽量在最内层。

由以下几步组成

  1. 找到网络请求发起的动作 request
  2. 找到网络请求返回的动作 response
  3. OCMExpect(response).andDo(),替换 mock data
  4. OCMStub(request).andCall(response),直接转到 mock 返回
  5. 然后用mock请求接口就好了
// 模拟 af manager
id mockAf = OCMPartialMock([mockUtil getAFHTTPSessionManager]);
OCMStub([mockUtil getAFHTTPSessionManager]).andReturn(mockAf);

// 模拟验证码接口数据
OCMExpect([mockUtil requestFinished:OCMOCK_ANY task:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
// 模拟数据
});
// 请求随即返回指定响应方法
OCMStub([mockAf POST:OCMOCK_ANY parameters:OCMOCK_ANY progress:OCMOCK_ANY success:OCMOCK_ANY failure:OCMOCK_ANY])
.andCall(mockUtil, @selector(requestFinished:task:));

[mockUtil 请求接口方法];
// 验证mock的方法是否真的调用成功
[mockDelegate verify];

// 验证调用了这个方法
OCMVerify([mockDelegate tableView]);

Notification通知模拟

新的 ocmock 已经完全用 XCTNSNotificationExpectation 来代替就方法了。

// 1.能验证收到通知,但是会真的继续执行 post notification 后面的方法
NSNotificationCenter *ntfCenter = [NSNotificationCenter defaultCenter];
XCTNSNotificationExpectation *ntfExcept = [[XCTNSNotificationExpectation alloc] initWithName:notificationName object:nil notificationCenter:ntfCenter];
.
.
.
[self waitForExpectations:@[ntfExcept] timeout:0.1];

// 2.能收到通知,并能捕获post方法直接返回,而不继续执行。
XCTNSNotificationExpectation *ntfExcept = [[XCTNSNotificationExpectation alloc] initWithName:kNotificationAddPickUpSuc];
[self expectationForNotification:kNotificationAddPickUpSuc object:nil handler:^BOOL(NSNotification * _Nonnull notification) {
[ntfExcept fulfill];
return true;
}];
.
.
.
[self waitForExpectationsWithTimeout:0.1 handler:^(NSError * _Nullable error) {}];

// 3.就是因为1的原因,发现就算用了2的方法,不知道为什么有时候不能捕获post,所以直接忽略掉方法不执行任何内容
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
id mocknf = OCMPartialMock(center);
// 忽视掉这个方法,免的crash
OCMStub([mocknf postNotificationName:OCMOCK_ANY object:OCMOCK_ANY]).andDo(nil);

问题

开始卡住的地方有两个;然后陆续发现一些别的。

stub expect 区别

Using OCMock to create iOS Unit Tests
Both –stub and –expect allow you to force a method to return a certain value whenever called during your test so you don’t have to worry about its result. In the example above, I am forcing the getPlaceName to return @”JFK International Airport”.

So, what is the difference between –stub and –expect?

The main difference is: you –expect methods that must happen, and –stub methods that might happen.

There are 2 ways mock objects fail: either an unexpected/unstubbed method is called, or an expected method is not called:

  1. Unexpected invocations. When a mock object receives a message that hasn’t been either stubbed or expected, it throws an exception immediately and your test fails.
  2. Expected invocations. When you call verify on your mock (generally at the end of your test), it checks to make sure all of the methods you expected were actually called. If any were not, your test will fail.

verify private method

虽然还在找 verify 私有(没有声明在h文件里的)方法,但是这句话值得记下来。
找到了Unit testing private method - objective C

If a method is private, you never should test it.
Think about this. You should test behaviour and contract of your methods instead internal implementation

模拟的data在哪里替换

尤其是看了很多例子都是 block 方式来替换的,但是 delegate 的方法到底如何才能调用/替换?尤其是data要在哪个步骤模拟?

  • 可以在 delegate 的 onfinished 替换,我选择了这里。
  • 可以在 requester 的 onfinished 替换?
//**********************************************************
//1.set up the mock players
//**********************************************************
__block ApiUtility *util = [[ApiUtility alloc] init];
ViewController *vc = [[ViewController alloc ] init];
//set the delegate and do the action
[util setDelegate:vc];

//**********************************************************
//2.mock a sceneraio and stub some fake data
//**********************************************************
id mockDelegate = [OCMockObject partialMockForObject:vc];
[[[mockDelegate expect] andDo:^(NSInvocation *invocation) {

//3.
//mocks we will pass back
id mockImage = [OCMockObject mockForClass:[UIImage class]];
id mockPhoto = [OCMockObject mockForClass:[Photo class]];
// we need to set these properties (to anything),
// because ViewController will try to use them on success of image download
[[[mockPhoto stub]andReturn:[OCMArg any]] photoId];
[[[mockPhoto stub]andReturn:[OCMArg any]] photoTitle];
[[[mockPhoto stub]andReturn:[OCMArg any]] ownerName];

[invocation setTarget:vc];
[invocation setSelector:@selector(apiUtility:didCompleteDownloadingImage:forPhoto:)];

//again 0 and 1 are reserved for the invocation object , so...
[invocation setArgument:&util atIndex:2];//apitutil
[invocation setArgument:&mockImage atIndex:3];//fake returned image
[invocation setArgument:&mockPhoto atIndex:4];//the mock photo
[invocation invoke];//actually involke apiUtility:didCompleteDownloadingImage:forPhoto:
}]
apiUtility:[OCMArg any] didCompleteDownloadingImage:[OCMArg any] forPhoto:[OCMArg any]];

如何立刻让网络请求返回到第一步模拟的 onfinished

写完一个满足条件后,直接就发现,如果不让网络请求发生而直接返回结果?
其实想想肯定有个地方需要指定这个关系,一请求就立马执行返回函数,也就是这里

//4. the mock object that will mock a download/ApiUtility
id mockUtil = [OCMockObject partialMockForObject:util];
[[[mockUtil stub]
andCall:@selector(apiUtility:didCompleteDownloadingImage:forPhoto:) onObject:mockDelegate] //we must call the protocol, as the real method would fail without network
downloadLargeImage:[OCMArg any]];

//5. do the download
[mockUtil downloadLargeImage:[OCMArg any]];

//6. make sure the delegate got the call
[mockDelegate verify];//make sure out delegate method is actually called

怎么跳过或者处理每次都执行的 didFinishLaunchingWithOptions

测试其他方法的时候,每次都要执行 launch 流程,导致 launch 相关的接口都会执行一遍,而这些接口要怎么模拟?

参考

ocmock 方面

Improve Unit Test with OCMock
Unit testing private method - objective C
OCMock使用实例
Kiwi 使用进阶 Mock, Stub, 参数捕获和异步测试
iOS测试系列(四):Specta,Expecta和Kiwi的使用
ocmock-cheatsheet.m
Using OCMock to create iOS Unit Tests
How to mock response from AFNetworking with OCMock in Swift?
Faking network calls for iOS unit tests
iOS Unit Testing - Switching method implementations with OCMock (Part 3)
iOS开发 | 如何为网络接口编写单元测试
为网络接口编写单元测试(OCMock注入)
Reference

ocmock 之外 uitest

Three ways UI Testing just made test-driven development even better
Recording Your XCTests on CircleCI
iOS — Separation of XCUnit & XCUITest jobs in CircleCI for single & multi app regions/variants
如果几十万的评论模拟,mock json 也太难搞了看下这个
PICK一下,iOS自动化测试新方案出道