iOS中的测试:OCMock

作者: 齐滇大圣 | 来源:发表于2016-05-13 15:52 被阅读5737次

    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

    相关文章

      网友评论

      • a637237315f9:感谢分享
      • nlpjoe:看完了感觉测得东西都挺简单的,但是操作挺麻烦的,有种得不偿失的感觉。您平时都在用mock吗?
        齐滇大圣:@周俊佐 我也没用,就了解了一下,感觉这种东西就大公司可以弄。小公司根本没精力

      本文标题:iOS中的测试:OCMock

      本文链接:https://www.haomeiwen.com/subject/ijcmrttx.html