iOS中的测试:OCMock

Mock介绍

什么是mock测试?

对于一些不容易构造或不容易获取的对象,此时你可以创建一个虚拟的对象(mock object)来完成测试。

例如你可能要尝试100次才会返回一个NSError,通过mock object你可以自行创建一个NSError对象,测试在出错情况下程序的处理是否符合你的预期。

例如你要连接服务器但是服务器在实验室,你在外工作的时候就无法测试了(小弟就试过这种情况,非常反感),这个时候你可以创建一个虚拟的服务器,并返回一些你指定的数据,从而绕过服务器。

例如假设你要访问一个数据库,但是访问过程的开销巨大,这时你可以虚拟一个数据库,并且返回一些自行定制的数据,从而绕过了数据库的访问。

mock的思想很简单:没有条件?我们就自行创造条件。

OCMock介绍

OCMock是一个用于为iOS或Mac OS X项目配置Mock测试的开源项目。

其实现思想就是根据要mock的对象的class来创建一个对应的对象,并且设置好该对象的属性和调用预定方法后的动作(例如返回一个值,调用代码块,发送消息等等),然后将其记录到一个数组中,接下来开发者主动调用该方法,最后做一个verify(验证),从而判断该方法是否被调用,或者调用过程中是否抛出异常等。

其实就是可以把它当做我们伪造的一个对象,我们给它一些预设的值之类的,然后就可以进行对应的验证了。

配置

到OCMock的官网下载dmg文件,打开后里面有个iOS library文件夹。把iOS library里的文件加入到你的项目里,按这篇教程来进行配置。

OCMock文档,可以到这里查看如何详细使用OCMock。

例子中使用到的方法说明

(以下数字为OCMock文档中的数字目录)

1.1. Class mocks

id classMock = OCMClassMock([SomeClass class]);

创建mock object当做类的实例。

2.1. Stubbing methods that return objects

OCMStub([mock someMethod]).andReturn(anObject);

告诉mock object当它调用someMethod的时候应该返回anObject。

3.1. Verify-after-running

id mock = OCMClassMock([SomeClass class]);

/* run code under test */

OCMVerify([mock someMethod]);

/* run code under test */写一些对应的代码,然后 OCMVerify([mock someMethod]);验证方法是否被调用。

3.2. Stubs and verification

id mock = OCMClassMock([SomeClass class]);
OCMStub([mock someMethod]).andReturn(myValue);

/* run code under test */

OCMVerify([mock someMethod]);

4.1. The any constraint

OCMStub([mock someMethodWithAnArgument:[OCMArg any]])
OCMStub([mock someMethodWithPointerArgument:[OCMArg anyPointer]])
OCMStub([mock someMethodWithSelectorArgument:[OCMArg anySelector]])

实际例子

以下的例子Demo:DSOCMockDemo

例子1

使用了文档中的OCMock文档中以下几种方法:
1.1 Class mocks
2.1 Stubbing methods that return objects
3.1 Verify-after-running
3.2 Stubs and verification
4.1 The any constraint

这是一个来自于OCMock上的例子。我这里就做一下翻译和Demo。

为了使我们更加具体的理解使用OCMock,这里假设我们写了一个接收来自于Twitter信息的应用。

有一个TwitterViewController类,一个TwitterConnection用来调用Twitter API得到数据,和一个TweetView类用来显示tweet对象。

下面是TwitterViewController类,有connectiontweetView对象。

@interface TwitterViewController : UIViewController

@property(nonatomic, strong)TwitterConnection *connection;
@property(nonatomic, strong)TweetView         *tweetView;

- (void)updateTweetView;

@end

TwitterConnection是一个网络连接类,有一个fetchTweets方法用来接收请求回来的信息。返回一个Tweet对象的数组。

@interface TwitterConnection : NSObject

- (NSArray *)fetchTweets;

@end

TweetView有一个addTweet:方法用来添加每一个Tweet对象到页面上。

@interface TweetView : UIView

- (void)addTweet:(Tweet *)aTweet;

@end

为什么使用mocks用来测试

当我们为updateTweetView来写一个测试类的时候我们要考虑它有哪些相对应的依赖,也就是TwitterConnection和TweetView。在这个例子里我们需要实例化一个真正的TwitterConnection对象来请求真实数据然后使用它。这样的话会有几个问题:

  • 使用真实的connection会使测试变慢,因为它还要去请求网络。
  • 我们永远不知道每一次Twitter返回的数据是什么。
  • 很难测试错误的返回,因为Twitter一般不会返回错误。

解决的方法就是伪造一个假的connection,既一个stub。

下图就是使用测试的代码:

iOS中的测试:OCMock_第1张图片

例子2

OCMock会在mock实例上没有找到相同名字的实例方法的时候去找同名的类方法。

使用例子一种的类,给TwitterConnection加个类方法
+ (NSArray *)fetchTweets2;

我们可以看到例子2和例子中classMethod和instanceMethod的stub方式一样。

例子3

使用了文档中的OCMock文档中以下几种方法:
1.3 Strict class and protocol mocks
7.1 Expect-run-verify
7.2 Strict mocks and failing fast
7.3 Stub actions and expect

当我们使用普通的mock的时候是这样的:

- (void)testStrictMock3{

    id classMock = OCMClassMock([TweetView class]);
    
    //设置期望或预设,这个classMock需要执行addTweet方法且参数不为nil。  不然的话会抛出异常
    //OCMExpect([classMock addTweet:[OCMArg isNotNil]]);
    //OCMStub([classMock addTweet:[OCMArg isNotNil]]);

    /* 如果不执行以下代码的话会抛出异常 */
    Tweet *testTweet = [[Tweet alloc] init];
    testTweet.userName = @"齐滇大圣";
    [classMock addTweet:testTweet];

    OCMVerifyAll(classMock);
}

这表示一种友好的mock,不会在没有OCMExpect或OCMStub设置类的所有方法时抛出异常。以上代码把OCMExpect和OCMStub注释掉时不会报错。

还有一种表示严格的mock:OCMStrictClassMock,如果把OCMExpect和OCMStub注释掉时会报错,它要求你执行类中的所有方法,所以比较适合用来测试必须实现的方法,代码如下:

- (void)testStrictMock3{
    id classMock = OCMStrictClassMock([TweetView class]);
    //OCMExpect([classMock addTweet:[OCMArg isNotNil]]);
    //OCMStub([classMock addTweet:[OCMArg isNotNil]]);

    Tweet *testTweet = [[Tweet alloc] init];
    testTweet.userName = @"齐滇大圣";
    [classMock addTweet:testTweet]; 

    OCMVerifyAll(classMock);
}

参考

ocmock源码
[iOS单元测试系列]单元测试编码规范
[iOS单元测试系列]-译-OCMock常见使用方式
Introduction to mocking with OCMock

你可能感兴趣的:(iOS中的测试:OCMock)