(一)置换方法(存根):告诉 mock 对象,当 someMethod 被调用,返回什么值
调用方式:
d jalopy = [OCMock mockForClass[Car class]];
OCMStub([jalopy goFaster:[OCMArg any] units:@"kph"]).andReturn(@"75kph");
使用场景:
1. 验证 A 方法时,A 方法内部使用 B 方法的返回值但是 B 方法内部逻辑比较复杂,这时需要使用 stub 方法去存根 B 方法的返回值。代码实现类似下面代码实现固定 funcB 的返回值,做到在不影响源代码的条件下,获取满足测试需要的参数。
方法进行存根前
- (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];
}
使用stub(mockObject getOtherTimeStrWithString).andReturn(@"1000")存根后类似于以下效果
- (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. 代码正常流程经过测试已经很健壮了,但是一些错误的流程并不容易发现但是是可能存在的,例如边缘值数据,单元测试中可以使用存根对数据进行模拟,测试代码在特殊数据情况下的运行情况。
注:stub()也可以不设置返回值,验证可行,猜测可能是返回的nil或者void,所以不带返回值的方法也可以进行方法存根。
(二)生成 Mock 对象,目前有三种方式。
通过对Person类的talk方法进行测试举例,其中也涉及Men类以及Animaiton类,以下是三个类的相关源码。
Person类
@interface Person()
@property(nonatomic,strong)Men *men;
@end
@implementation Person
-(void)talk:(NSString *)str
{
[self.men logstr:str];
[Animaiton logstr:str];
}
@end
Men类
@implementation Men
-(NSString *)logstr:(NSString *)str
{
NSLog(@"%@",str);
return str;
}
@end
Animaiton类
@implementation Animaiton
+(NSString *)logstr:(NSString *)str
{
NSLog(@"%@",str);
return str;
}
-(NSString *)logstr:(NSString *)str
{
NSLog(@"%@",str);
return str;
}
@end
对talk方法进行单测时需要对person类进行mock,以下是通过三种不同的方式生成mock对象,对三种方式的调用方法,使用场景都做了介绍,最后对每种方式的优缺点也做了一个表格方便区别。
Nice Mock
NiceMock 创建的 mock 对象在进行方法测试时会优先调用实例方法,若未找到实例方法,会继续调用同名的类方法。因此该方法可以用来生成mock对象去测试类方法也可以测试对象方法。
使用方式:
- (void)testTalkNiceMock {
id mockA = OCMClassMock([Men class]);
Person *person1 = [Person new];
person1.men = mockA;
[person1 talk:@"123"];
OCMVerify([mockA logstr:[OCMArg any]]);
}
使用场景:
Nice mock 是比较友好的,当一个没有存根的方法被调用时他不会引起一个异常会验证通过。如果你不想自己对很多的方法进行存根,那么使用 nice mock。在上方的举例中mockA调用testTalkNiceMock时,Men类中的+(NSString *)logstr:(NSString *)str不会执行打印操作。在调用过程中因为同时存在同名的logstr:类方法和实例方法,会优先调用实例方法。
Strict Mock
使用方式:
测试case如下,mockA是Strict Mock生成要调用testTalkStrictMock方法,则Mock生成要调用testTalkStrictMock方法则该方法要使用stub进行存根,否则最后的OCMVerifyAll(mockA)就会抛出异常。
- (void)testTalkStrictMock {
id mockA = OCMStrictClassMock([Person class]);
OCMStub([mockA talk:@"123"]);
[mockA talk:@"123"];
OCMVerifyAll(mockA);
}
使用场景:
这种方式创建的 mock 对象,如果调用未 stub(stub 代表存根)的方法,会抛出一个异常。这需要保证在 mock 的生命周期中每一个独立调用的方法都是被存根的,这种方法使用比较严格,很少使用。
Partial Mock
这样创建的对象在调用方法时:如果方法被 stub,调用 stub 后的方法,如果方法没有被 stub,调用原来的对象的方法,该方法有限制只能 mock 实例对象。
使用方式:
- (void)testTalkPartialMock {
id mockA = OCMPartialMock([Men new]);
Person *person1 = [Person new];
person1.men = mockA;
[person1 talk:@"123"];
OCMVerify([mockA logstr:[OCMArg any]]);
}