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。

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

例子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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容