Detailed explanation of OCMock, a common framework for iOS unit testing

Table of contents

1. Unit testing

1.1 The necessity of unit testing

1.2 Purpose of unit testing

1.3 The two main frameworks that unit testing depends on

2. Integration and use of OCMock

2.1 OCMock integration method

2.2 How to use OCMock

2.3 mock use restrictions

Three, finally

END Supporting Learning Resources Sharing


1. Unit testing

1.1 The necessity of unit testing

Test-driven development is not a very new concept. In daily development, testing is often required, but this kind of output is something that must be displayed on the screen after clicking a series of buttons. When testing, you often use the simulator to start the app from scratch again and again, then locate the program of the module you are in, do a series of click operations, and then check whether the results meet your expectations.

This behavior is undoubtedly a huge waste of time. So many senior engineers found that we can construct a similar scene in the code, and then call the code we want to check before in the code, and compare the running results with the expected results in the program. If they are consistent, then It shows that there is no problem with our code, and thus unit tests are generated.

1.2 Purpose of unit testing

The main purpose of unit testing is to find the internal logic, syntax, algorithm and functional errors of the module.

Unit testing is mainly based on white box testing to verify the following issues:

  • Verify that the code matches the design.
  • Find errors in design and requirements.
  • Spots errors introduced during coding.

Unit testing focuses on the following parts:

Independent Path - Tests for basic execution paths and loops, possible errors are:

  • Comparison of different data types.
  • "Mistake 1 error", that is, one more cycle or one less cycle may be possible.
  • False or impossible termination condition.
  • Inappropriate modification of a loop variable.

Local Data Structures - Local data structures of cells are the most common source of errors and test cases should be designed to check for possible errors:

  • Inconsistent data types.
  • Check for incorrect or inconsistent data types.

Error handling - a relatively complete unit design should be able to foresee the error conditions, and set up appropriate error handling, so that when the program makes an error, the error can be rearranged to ensure the correctness of the period logic:

  • The description of the error is difficult to understand.
  • The displayed error does not match the actual error.
  • Incorrect handling of error conditions.

Boundary Conditions - Errors at the boundaries are the most common error symptoms:

  • An error occurred when taking the maximum and minimum values.
  • Greater-than and less-than comparisons in control flow are often erroneous.

Unit interface - the interface is actually a collection of corresponding relationships between input and output. Dynamic testing of a unit is nothing more than giving the unit an input, and then checking whether the output is consistent with expectations. If the data cannot be input and output normally, the unit test is out of the question, so the unit interface needs to be tested as follows:

  • Whether the number, properties and sequence of the input and output of the unit under test are consistent with the description in the detailed design.
  • Whether to modify the formal parameters that are only used for input.
  • Whether constraints are passed through formal parameters.

1.3 The two main frameworks that unit testing depends on

OCUnit (that is, testing with XCTest) is actually Apple's own testing framework, which is mainly used for assertion. Because it is simple to use, this article will not introduce too much.

The main function of OCMock is to simulate the return value of a method or property. You may wonder why you should do this? Use the model object generated by the model, and then pass it in? The answer is yes, but there are special cases, such as some objects that are not easy to construct or obtain, at this time you can create a dummy object to complete the test. The implementation idea is to create a corresponding object according to the class of the object to be mocked, and set the properties of the object and the actions after calling the predetermined method (such as returning a value, calling a code block, sending a message, etc.), and then Record it into an array, then the developer actively calls the method, and finally performs a verify (verification) to determine whether the method is called, or whether an exception is thrown during the call, etc. It is also unclear how to use OCMock that is more difficult to use in unit test development. This article mainly talks about the integration and use of this OCMock.

2. Integration and use of OCMock

2.1 OCMock integration method

The project integrates the OCMock third-party library, which can be directly installed with the OCMock framework using the pod tool. If you use the iBiu tool to install the OCMock library, you need to create Podfile.custom at the same level as the podfile.

Add OCmock using the same format as a normal pod file as follows:

source 'https://github.com/CocoaPods/Specs.git'
pod 'OCMock'

2.2 How to use OCMock

(1) Replacement method (stub): Tell the mock object what value to return when someMethod is called

Call method:

d jalopy = [OCMock mockForClass[Car class]];
OCMStub([jalopy goFaster:[OCMArg any] units:@"kph"]).andReturn(@"75kph");

scenes to be used:

1. When verifying method A, method A uses the return value of method B internally, but the internal logic of method B is more complicated. At this time, the stub method needs to be used to stub the return value of method B. The code implementation is similar to the following code to implement the fixed return value of funcB, so that the parameters that meet the test requirements can be obtained without affecting the source code.

method before stubbing

- (NSString *)getOtherTimeStrWithString:(NSString *)formatTime{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"]; //(@"YYYY-MM-dd hh:mm:ss") ----------设置你想要的格式,hh与HH的区别:分别表示12小时制,24小时制
    //设置时区选择北京时间
    NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:@"Asia/Beijing"];
    [formatter setTimeZone:timeZone];
    NSDate* date = [formatter dateFromString:formatTime]; //------------将字符串按formatter转成nsdate
    //时间转时间戳的方法:
    NSInteger timeSp = [[NSNumber numberWithDouble:[date timeIntervalSince1970]] integerValue] * 1000;
    return [NSString stringWithFormat:@"%ld",(long)timeSp];
}

After using stub(mockObject getOtherTimeStrWithString).andReturn(@"1000") stub is similar to the following effect

- (NSString *)getOtherTimeStrWithString:(NSString *)formatTime{
    
    return @"1000";
    
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"]; //(@"YYYY-MM-dd hh:mm:ss") ----------设置你想要的格式,hh与HH的区别:分别表示12小时制,24小时制
    //设置时区选择北京时间
    NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:@"Asia/Beijing"];
    [formatter setTimeZone:timeZone];
    NSDate* date = [formatter dateFromString:formatTime]; //------------将字符串按formatter转成nsdate
    //时间转时间戳的方法:
    NSInteger timeSp = [[NSNumber numberWithDouble:[date timeIntervalSince1970]] integerValue] * 1000;
    return [NSString stringWithFormat:@"%ld",(long)timeSp];
}

2. The normal process of the code has been tested and is very robust, but some wrong processes are not easy to find but may exist, such as edge value data, stubs can be used to simulate the data in the unit test, and the test code runs under special data conditions Condition.

Note: stub() can also not set the return value, and the verification is feasible. The guess may be the returned nil or void, so methods without return values ​​can also be used for method stubs.

(2) There are currently three ways to generate a Mock object.

Through the test example of the talk method of the Person class, which also involves the Men class and the Animaiton class, the following are the relevant source codes of the three classes.

Person class

@interface Person()
@property(nonatomic,strong)Men *men;
@end


@implementation Person
-(void)talk:(NSString *)str
{
    [self.men logstr:str];
    [Animaiton logstr:str];
    
}
@end

Door class

@implementation Men
-(NSString *)logstr:(NSString *)str
{
    NSLog(@"%@",str);
    return str;
}
@end

Animaton category

@implementation Animaiton
+(NSString *)logstr:(NSString *)str
{
    NSLog(@"%@",str);
    return str;
}
-(NSString *)logstr:(NSString *)str
{
    NSLog(@"%@",str);
    return str;
}
@end

When performing a single test on the talk method, it is necessary to mock the person class. The following are three different methods to generate mock objects. The calling methods and usage scenarios of the three methods are introduced. Finally, the advantages and disadvantages of each method are also introduced. Made a table to facilitate the distinction.

Nice Mock

The mock object created by NiceMock will call the instance method first when performing method testing. If the instance method is not found, it will continue to call the class method with the same name. Therefore, this method can be used to generate mock objects to test class methods and object methods.

How to use:

- (void)testTalkNiceMock {
    id mockA = OCMClassMock([Men class]);
    Person *person1 = [Person new];
    person1.men = mockA;
    [person1 talk:@"123"];
    OCMVerify([mockA logstr:[OCMArg any]]);
}

scenes to be used:

Nice mock is more friendly, when a method without a stub is called, it will not cause an exception and will pass the verification. If you don't want to stub many methods yourself, use nice mocks. In the above example, when mockA calls testTalkNiceMock, +(NSString *)logstr:(NSString *)str in the Men class will not execute the printing operation. During the calling process, because there are logstr: class methods and instance methods with the same name at the same time, the instance method will be called first.

Strict Mock

How to use:

The test case is as follows, mockA is generated by Strict Mock and the testTalkStrictMock method is called, and the testTalkStrictMock method is called when the Mock is generated, then the method needs to use a stub for the stub, otherwise the final OCMVerifyAll (mockA) will throw an exception.

- (void)testTalkStrictMock {
    id mockA = OCMStrictClassMock([Person class]);
    OCMStub([mockA talk:@"123"]);
    [mockA talk:@"123"];
    OCMVerifyAll(mockA);
}

scenes to be used:

The mock object created in this way will throw an exception if the method without stub (stub stands for stub) is called. This needs to ensure that every independently invoked method is stubbed during the life cycle of the mock. This method is strictly used and rarely used.

Partial Mock

When the object created in this way calls the method: if the method is stub, call the method after the stub, if the method is not stub, call the method of the original object, which is limited to the mock instance object.

How to use:

- (void)testTalkPartialMock {
    id mockA = OCMPartialMock([Men new]);
    Person *person1 = [Person new];
    person1.men = mockA;
    [person1 talk:@"123"];
    OCMVerify([mockA logstr:[OCMArg any]]);
}

scenes to be used:

When calling a method that is not stubbed, the actual object's method is called. This technique is useful when stubbing a method of a class does not work well. +(NSString *)logstr:(NSString *)str in the Men class will perform the printing operation when calling testTalkPartialMock.

Three way diff table:

(3) Call of the verification method

Call method:

OCMVerify([mock someMethod]);
OCMVerify(never(),    [mock doStuff]); //从没被调用
OCMVerify(times(n),   [mock doStuff]);   //调用了N次
OCMVerify(atLeast(n), [mock doStuff]);  //最少被调用了N次
OCMVerify(atMost(n),  [mock doStuff]);

scenes to be used:

In the unit test, you can verify whether a method is executed and how many times it is executed.

Delayed verification call:

OCMVerifyAllWithDelay(mock, aDelay);

Usage scenario: This function is used to wait for asynchronous operations more often, where aDelay is the expected longest waiting time.

(4) Add expectations

Call method:

Prepare data:

NSDictionary *info = @{@"name": @"momo"};
id mock = OCMClassMock([MOOCMockDemo class]);

Add expected:

OCMExpect([mock handleLoadSuccessWithPerson:[OCMArg any]]);

Can be expected not to execute:

OCMReject([mock handleLoadFailWithPerson:[OCMArg any]]);

Parameters can be validated:

// 预期 + 参数验证
OCMExpect([mock handleLoadSuccessWithPerson:[OCMArg checkWithBlock:^BOOL(id obj) {
    MOPerson *person = (MOPerson *)obj;
    return [person.name isEqualToString:@"momo"];
}]]);

The order of execution can be expected:

// 预期下列方法顺序执行
[mock setExpectationOrderMatters:YES];
OCMExpect([mock handleLoadSuccessWithPerson:[OCMArg any]]);
OCMExpect([mock showError:NO]);

Arguments can be ignored (when method execution is expected):

OCMExpect([mock showError:YES]).ignoringNonObjectArgs; // 忽视参数

implement:

[MOOCMockDemo handleLoadFinished:info];

Affirmation:

OCMVerifyAll(mock);

Assertions can be delayed:

OCMVerifyAllWithDelay(mock, 1); // 支持延迟验证

The last OCMVerifyAll will verify whether the previous expectations are valid, as long as one is not called, an error will occur.

(5) Parameter constraints

Call method:

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

Usage scenario: When using the OCMVerify() method to verify whether a method is called, the unit test will verify whether the method parameters are consistent. If they are inconsistent, it will prompt that the verification failed. At this time, if you only pay attention to the method call and do not pay attention to the parameters, you can use [ OCMArg any] to pass parameters.

(6) Simulation of network interface

As the name suggests, it can mock the data return of the network interface to test the direction and accuracy of the code under different data.

Call method:

id mockManager = OCMClassMock([JDStoreNetwork class]);
[orderListVc setComponentsNet:mockManager];
[OCMStub([mockManager startWithSetup:[OCMArg any] didFinish:[OCMArg any] didCancel:[OCMArg any]]) andDo:^(NSInvocation *invocation) {   


    void (^successBlock)(id components,NSError *error) = nil;   
    
    [invocation getArgument:&successBlock atIndex:3];  
    
    successBlock(@{@"code":@"1",@"resultCode":@"1",@"value":@{@"showOrderSearch":@"NO"}},nil);
    }];

The above is to call the interface inside the call to the setComponentsNet method, which can simulate the required return data after calling the interface, and the test data returned in the successBlock. This method is to obtain the method signature of the interface call, obtain the successBlock success callback pass parameter and call it manually. It is also possible to simulate the failure of the interface, as long as the corresponding failure callback in the signature is obtained, it can be realized.

Usage scenario: When writing a unit test method, the simulation of the network interface is involved, and the mock interface returns the result in this way.

(7) Restoration

After replacing the class method, the class can be restored to its original state by calling stopMocking.

Call method:

id classMock = OCMClassMock([SomeClass class]);
/* do stuff */
[classMock stopMocking];

scenes to be used:

After the instance object is replaced normally, stopMocking will be called automatically after the mock object is released, but the mock object added to the class method will span multiple tests, the mock class object will not be deallocated after the replacement, and the mock relationship needs to be canceled manually .

(8) Observer simulation - create an instance that accepts notifications

Call method:

- (void)testPostNotification {   
Person *person1 = [[Person alloc] init];   
id observerMock = OCMObserverMock();   
//给通知中心设置观察者    
[[NSNotificationCenter defaultCenter] addMockObserver: observerMock name:@"name" object:nil];    
//设置观察期望    
[[observerMock expect] notificationWithName:@"name" object:[OCMArg any]];    //调用要验证的方法    
[person1 methodWithPostNotification];    
[[NSNotificationCenter defaultCenter] removeObserver:observerMock];    
// 调用验证   
OCMVerifyAll(observerMock);}

scenes to be used:

Create a mock object that can be used to observe notifications. Mocks must be registered to receive notifications.

(9) Mock protocol

Call method:

id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));
/*严格的协议*/
id classMock = OCMStrictClassMock([SomeClass class]);
id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));
id protocolMock = OCMProtocolMock(@protocol(SomeProtocol));
/*严格的协议*/
id classMock = OCMStrictClassMock([SomeClass class]);
id protocolMock = OCMStrictProtocolMock(@protocol(SomeProtocol));

Invocation scenario: used when an instance needs to be created to have the functions defined by the protocol.

2.3 mock use restrictions

For the same method, it is not possible to stub first and then expect: because if you stub first, all calls will become stubs, so even if the method is called by the process, the OCMVerifyAll verification will fail in the end; the solution is to use OCMExpect by the way Stub, for example: OCMExpect([mock someMethod]).andReturn(@"a string"), or place the stub after expect.

Partial mocking does not apply to certain classes: such as NSString and NSDate, these "toll-free bridged" classes will throw exceptions otherwise.

Some methods cannot be stub: such as: init, class, methodSignatureForSelector, forwardInvocation, etc.

The class methods of NSString and NSArray cannot be stub, otherwise they are invalid.

NSObject method calls cannot be validated unless overridden in a subclass.

Private method calls of Apple's core classes cannot be verified, such as methods starting with _.

Delayed verification method calls are not supported, and only the delayed verification of the expect-run-verify mode is currently supported.

OCMock does not support multithreading.

Three, finally

Hopefully this article and examples have clarified some of the most common uses of OCMock. Mocking is monotonous but necessary for an application. If a method is difficult to test with mocks, it's a sign that your design needs to be rethought.

If the article is helpful to you, remember to like, bookmark, and add attention. I will share some dry goods from time to time...

END Supporting Learning Resources Sharing

Finally:  In order to give back to the die-hard fans, I have compiled a complete software testing video learning tutorial for you. If you need it, you can get it for free 【保证100%免费】

Software Testing Interview Documentation

We must study to find a high-paying job. The following interview questions are the latest interview materials from first-tier Internet companies such as Ali, Tencent, and Byte, and some Byte bosses have given authoritative answers. Finish this set The interview materials believe that everyone can find a satisfactory job.

insert image description here

How to obtain the full set of information:

Guess you like

Origin blog.csdn.net/IT_LanTian/article/details/131287604