swift unit test (g) using the simulation framework constituents of OCMock

This article describes how to use OCMock framework swift project

1, Mock Introduction

       OCMock is used to configure a Mock test for iOS or Mac OS X project open source project.

       The idea is to create its implementation object corresponding to the object class in accordance with the mock, and set properties and the method of operation of the call to a predetermined object (e.g., a return value, the code block is called, send messages, etc.), then record into an array, then the developer active call this method to make a final verify (verification), so as to determine whether the method is called, the calling procedure or the like whether or not an exception is thrown.

      In fact, we can think of it as an object of counterfeiting, we give it some preset value and the like, then you can carry on the corresponding verified.

      The reason for using mock: You can return the analog data to verify whether the method call.

 

2, OCMock use

1) Configuration OCMock

OCMock configuration See: http://ocmock.org/ios/

(1) on github download OCMock , which open / Examples / SwiftExamples , a copy of which usr folder to a project in your area

(2) Select the targets in the target test -> Build Phases -> Libraries are introduced into the Link Binary With usr-> lib-> libOCMock.a file

(3) Set Build Settings -> Search other_ld, find the Other Linker Flags -> Add -ObjC

(4) Set Build Settings -> Search header search, find Header Search Paths -> Add $ (PROJECT_DIR) / usr / include

(5)创建一个VC类和Connection类,用于mock测试

TwitterViewController.swift

import Foundation

@objcMembers
class TwitterViewController: NSObject {

    var connection: Connection
    var data: String
    
    class func newController() -> TwitterViewController {
        return TwitterViewController()
    }
    
    override init() {
        self.connection = TwitterConnection()
        self.data = ""
    }
    
    func redisplay() {
        data = connection.fetchData()
    }

}

TwitterConnection.swift

import Foundation


//网络连接类
@objc
protocol Connection {
    func fetchData()->String
}

@objcMembers
class TwitterConnection: NSObject, Connection{

    func fetchData() -> String {
        return "real data returned from other system"
    }
}

(6)由于是在swift项目中使用OCMock,其许多语法在swift中无法实现,所以测试文件使用OC语言书写

在文件中导入头文件

#import <OCMock/OCMock.h>

在oc文件中使用swift文件,需要导入头文件

#import "OCMockTests-Swift.h"

测试一个简单的模型类

     创建一个person模型类

import UIKit
@objcMembers
class Person: NSObject {
    
    func getPersonName()->String{
        return "小李"
    }

}

     在测试文件中创建测试方法

- (void)testPersonNameEqual{
    Person *person = [[Person alloc] init];
    //创建一个mock对象
    id mockClass = OCMClassMock([Person class]);
    //可以给这个mock对象的方法设置预设的参数和返回值
    OCMStub([mockClass getPersonName]).andReturn(@"小李");
    
    //用这个预设的值和实际的值进行比较
    XCTAssertEqualObjects([mockClass getPersonName], [person getPersonName],@"值相等");
}

  mock类

例子1:使用OCMClassMock(类mock)来mock一个类对象

- (void)testMockingAnObject{
    
    //模拟出来一个网络请求链接的数据类
    id mockConnection = OCMClassMock([TwitterConnection class]);
    //模拟fetchdata方法返回预设置
    OCMStub([mockConnection fetchData]).andReturn(@"stubbed!");
    
    TwitterViewController *controller = [TwitterViewController newController];
    controller.connection = mockConnection;

    //这里执行redisplay之后,返回 stubbed
    [controller redisplay];
    
    //-------验证使用对应参数的方法是否被调用------
    
    //成功
    OCMVerify([mockConnection fetchData]);
    XCTAssertEqualObjects(@"stubbed!", controller.data, @"Excpected stubbed data in controller.");
    
    //失败
//    XCTAssertEqualObjects(@"real data returned from other system", controller.data, @"unExcpected stubbed data in controller.");
}


执行测试方法成功

主要运用于的场景:
当我们为vc来写一个测试类的时候我们要考虑它有哪些相对应的依赖,也就是TwitterConnection和data。在这个例子里我们需要实例化一个真正的TwitterConnection对象来请求真实数据然后使用它。这样的话会有几个问题:
 
        使用真实的connection会使测试变慢,因为它还要去请求网络。
        我们永远不知道每一次返回的数据是什么。
        很难测试错误的返回,因为一般不会返回错误。
 
 解决的方法就是伪造一个假的connection,既一个stub。

 

例子2:使用OCMPartialMock(部分mock)将新建的connection作为mock对象

- (void)testPartiallyMockingAnObject
{
    //新建一个connection用来mock
    TwitterConnection * testConnection = [TwitterConnection new];
    id mockConnection = OCMPartialMock(testConnection);
    OCMStub([mockConnection fetchData]).andReturn(@"stubbed!");
    
    TwitterViewController *controller = [TwitterViewController newController];
    [controller redisplay];
    
    //-------验证使用对应参数的方法是否被调用------
    //失败
    OCMVerify([mockConnection fetchData]);
    XCTAssertEqualObjects(@"stubbed!", controller.data, @"Excpected stubbed data in controller.");
}

运行测试方法,执行失败
原因是:

使用OCMPartialMock这样创建的对象在调用方法时:
 如果方法被stub,调用stub后的方法.
 如果方法没有被stub,调用原来的对象的方法.
 partialMock 对象在调用方法后,可以用于稍后的验证此方法的调用情况(被调用,调用结果)

 

例子3:使用OCMPartialMock(部分mock)将使用的是controller的connection作为mock对象

- (void)testPartiallyMockingAnObject2
{
    TwitterViewController *controller = [TwitterViewController newController];
    
    //从controller中获取mock对象
    id mockConnection = OCMPartialMock((NSObject *)controller.connection);
    OCMStub([mockConnection fetchData]).andReturn(@"stubbed!");
    
    [controller redisplay];
    
     //-------验证使用对应参数的方法是否被调用------
    //成功
    OCMVerify([mockConnection fetchData]);
    XCTAssertEqualObjects(@"stubbed!", controller.data, @"Excpected stubbed data in controller.");
}


运行测试方法,执行成功

基本语法:

关键字 说明
Mock 创建一个模拟对象,我们可以验证,修改它的行为
Stub Mock对象的函数返回特定的值
Partial Mock 重写Mock对象的方法

3、OCMock用法总结

1)创建mock对象

用途 创建方式
类mock

默认的mock方式是nice(方法调用的时候返回nil或者是返回正确的方法)

id classMock = OCMClassMock([SomeClass class]);

严格的类mock

严格的模式下,mock的对象在调用没有被stub(置换)的方法的时候,会抛出异常.

id classMock = OCMStrictClassMock([SomeClass class]);

协议mock

默认的mock方式是nice(方法调用的时候返回nil或者是返回正确的方法)

id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));

严格的协议mock

严格的模式下,mock的对象在调用没有被stub(置换)的方法的时候,会抛出异常.

id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));

部分mock

这样创建的对象在调用方法时:

如果方法被stub,调用stub后的方法.

如果方法没有被stub,调用原来的对象的方法.

partialMock 对象在调用方法后,可以用于稍后的验证此方法的调用情况(被调用,调用结果)

id partialMock = OCMPartialMock(anObject)

观察者mock

这样创建的对象可以用于观察/通知.

id observerMock = OCMObserverMock();

2)置换方法

置换方法类型 说明

置换方法(待置换的方法返回objects)

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

在mock对象上调用某个方法的时候,这个方法一定返回一个anObject.(也就是说强制替换了某个方法的返回值为anObject)

置换方法(待置换的方法返回values)

OCMStub([mock aMethodReturningABoolean]).andReturn(YES);

在mock对象上调用某个方法的时候,这个方法一定返回values. 注意这里的原始值类型一定要和原来的方法的返回值一致.

委托到另一个方法(置换委托方法到另外一个方法)

OCMStub([mock someMethod]).andCall(anotherObject, @selector(aDifferentMethod));

置换mock 对象的someMethod ==> anotherObject 的aDifferentMethod.

这样,当mock对象调用someMethod方法的时候,实际上的操作就是anotherObject 调用了aDifferentMethod方法.

置换一个blcok方法.

OCMStub([mock someMethod]).andDo(^(NSInvocation invocation) { / block that handles the method invocation */ }); 在mock对象调用someMethod的时候,andDo后面的block会调用.block可以从NSInvocation中得到一些参数,然后使用这个NSInvocation对象来构造返回值等等.

置换方法的参数

OCMStub([mock someMethodWithReferenceArgument:[OCMArg setTo:anObject]]);

 

OCMStub([mock someMethodWithReferenceArgument:[OCMArg setToValue:OCMOCK_VALUE((int){aValue})]]);

mock对象在调用某个带参数的方法的时候,这个方法的参数可以被置换. setTo用来设置对象参数,setToValue用来设置原始值类型的参数.

调用某个方法就抛出异常

OCMStub([mock someMethod]).andThrow(anException);

当mock对象调用someMethod的时候,就会抛出异常

调用某个方法就发送通知

OCMStub([mock someMethod]).andPost(aNotification);

当mock对象调用someMethod的时候,就会发送通知.

链式调用

OCMStub([mock someMethod]).andPost(aNotification).andReturn(aValue);

所有的actions(比如andReturn,andPost)可以链式调用.上面的例子中,mock对象调用someMethod方法后,发送通知,返回aValue

转发的原来的对象/类

OCMStub([mock someMethod]).andForwardToRealObject(); 使用部分mock的时候,使用类方法的可以转发到原来的对象/原来的类.

这个功能在链式调用或者是使用expectation的时候很有用.

什么也不做

OCMStub([mock someMethod]).andDo(nil);

可以给andDo传入nil参数,而不是原来一个block作为参数.

这个功能在使用部分mock/mock类的时候很有用,可以屏蔽原来的行为.

3)验证作用

验证时间点 说明

运行后就验证

id mock = OCMClassMock([SomeClass class]);

/* run code under test */

OCMVerify([mock someMethod]);

在mock对象调用someMethod后就开始验证.(如果这个方法没有被调用),就抛出一个错误. 在验证语句中可以使用 参数约束.

置换后验证

id mock = OCMClassMock([SomeClass class]);

 

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

/* run code under test */

OCMVerify([mock someMethod]);

在置换某个方法(置换了返回的参数)后,然后可以验证这个方法是否被调用.

 

4)类方法的mock

用途 说明

置换类方法

id classMock = OCMClassMock([SomeClass class]);

 

OCMStub([classMock aClassMethod]).andReturn(@"Test string");

// result is @"Test string"

NSString *result = [SomeClass aClassMethod];

置换类方法和置换实例方法的步骤相像.但是mock对象在深层次上对原有 类做了些更改.(替换了原有的的类的meta class).这让置换调用直接作用在mock对象上,而不是原有的类.

注意:

添加到类方法上的mock对象跨越了多个测试,mock的类对象在置换后不会deallocated,需要手动来取消这个mock关系.

如果mock对象作用于同一个类, 这时的行为就不预测了.

验证类方法的调用

id classMock = OCMClassMock([SomeClass class]);

/* run code under test */

OCMVerify([classMock aClassMethod]);

验证类方法的调用和验证实例方法的调用的使用方式一样.

有歧义的类方法和实例方法

id classMock = OCMClassMock([SomeClass class]);

 

OCMStub(ClassMethod([classMock ambiguousMethod])).andReturn(@"Test string");

// result is @"Test string"

NSString *result = [SomeClass ambiguousMethod];

置换了类方法,但是类有一个和类方法同名的实例方法,置换类方法的时候,必须使用ClassMethod()

恢复类

id classMock = OCMClassMock([SomeClass class]);

/* do stuff */

[classMock stopMocking];

置换类方法后,可以将类恢复到原来的状态,通过调用stopMocking来完成.

如果在结束测试前,需要恢复到原来的状态的话,这就很有用了. 在mock对象被释放的时候,stopMocking会自动调用.

当类恢复到原来的对象,类对象的meta class会变为原来的meta class.这会移除所有的方法置换.

在调用了stopMocking之后,不应该继续使用mock对象.

 

5)部分mock

用途 说明

置换方法

id partialMock = OCMPartialMock(anObject);

 

OCMStub([partialMock someMethod]).andReturn(@"Test string");

// result1 is @"Test string"

NSString *result1 = [partialMock someMethod];

// result2 is @"Test string", too!

NSString *result2 = [anObject someMethod];

部分Mock修改了原有的mock对象的类.(实际上是继承了待mock对象,然后替换用 继承的类来代替原有的类).

这就是说: 使用真实的对象来调用,即使是使用self,也会影响 置换方法和预期的结果.

验证方法调用

id partialMock = OCMPartialMock(anObject);

/* run code under test */

OCMVerify([partialMock someMethod]);

验证方法的调用和验证类方法,验证协议的调用类似.

恢复对象

id partialMock = OCMPartialMock(anObject);

/* do stuff */

[partialMock stopMocking];

真正的对象可以通过调用stopMocking方法来恢复到原来的状态.

这种情况只有在结束测试之前需要恢复到原来状态.

部分mock对象会在释放的时候,会自动调用 stopMocking方法.

当对象转变为原来的状态后,类会变为原来的类.也会移除所有的置换方法.

在调用了stopMocking之后,最好不要去使用mock对象.

6)观察者mock

用途 说明

准备工作

为观察者和通知创建一个mock对象.

id observerMock = OCMObserverMock();

在通知中心注册对象

[notificatonCenter addMockObserver:aMock name:SomeNotification object:nil];

预期会调用这个通知.

[[mock expect] notificationWithName:SomeNotification object:[OCMArg any]];

验证

OCMVerifyAll(observerMock);

目前观察者 mock 总是严格的mock.当一个不在预期中的通知调用的时候,就会抛出一个异常.

这就是说,单个的通知实际上不是能被验证的.所有的通知必须按照预期赖设置.他们会在通过调用OCMVerifyAll来一起验证.

4、总结:

优点:

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

2)可以模拟很难测试的错误的返回

3)单独依靠XCTest难以完成Mock或者Stub,OCMock用到Stub和Mock来模拟这些外部依赖的对象,从而控制它们

缺点:

在swift项目中使用OCMock,测试用例的编写必须使用OC语言

 

参考内容:

OC:OCMock 用法一二

iOS中的测试:OCMock

[iOS单元测试系列]-译-OCMock常见使用方式

Guess you like

Origin blog.csdn.net/lin1109221208/article/details/93051476