如何使用OCMock编写一个单元测试,用于复杂的方法,这些方法有很多“if conditions”和contions正在检查数组计数。这是我的示例方法:
- (BOOL)someMethod
{
BOOL bUploadingLocation = false;
CLLocation *newLocation = nil;
if([locationCallbacks count] > 0)
{
for(CLLocation* receivedlocation in locationCallbacks) {
//Print
}
newLocation = [locationCallbacks objectAtIndex:[locationCallbacks count] - 1];
}
else
{
return bUploadingLocation;
}
BOOL initialLocation = false;
CLLocation *lastKnownLocation = [[ASAppProfileService sharedInstance] getStoredLastKnownLocation];
if(lastKnownLocation == nil)
{
initialLocation = true;
}
BOOL checkedInstatusChange = false;
if([[CRMgr getSharedInstance] isCREnabled])
{
checkedInstatusChange = [[CRMgr getSharedInstance] orchestrateCROperationsWithLocation:newLocation];
}
BOOL lastGoodUploadIsWithinThresold = [MELocationUtils isLastAccurateUploadWithinThresold];
if(initialLocation || checkedInstatusChange || !lastGoodUploadIsWithinThresold)
{
BOOL bCanUpload = [[ASAppProfileService sharedInstance] canUploadData];
if(bCanUpload)
{
[[MEProtocolHelper sharedDataUploadManager] uploadLocationInformationPayload:newLocation];
bUploadingLocation = true;
}
else
{
//Print something
}
}
return bUploadingLocation;
}
如何为这些方法编写单元测试?
答案 0 :(得分:2)
正如您所注意到的,有许多不同的条件来控制此方法的输出。你需要计划编写几个测试(我会为每个条件写一个测试)。
在我的setup
方法中,我会为您呼叫的所有服务创建模拟:
ASAppProfileService
CRMgr
MELocationUtils
MEProtocolHelper
这些模拟在每次测试中都很方便,因此在每次测试的基础上设置它们都没有意义。
您还可以考虑在实例或类方法调用中包装这些服务:
+ (BOOL)isCheckedInStatusChange
{
BOOL checkedInStatusChange = NO;
if([[CRMgr getSharedInstance] isCREnabled])
{
checkedInStatusChange = [[CRMgr getSharedInstance] orchestrateCROperationsWithLocation:newLocation];
}
return checkedInStatusChange;
}
这可能会使嘲弄变得更容易。
但最终,没有捷径可以测试每组可能的输入并验证输出。
答案 1 :(得分:2)
要添加@Ben_Flynn的答案,您设计课程的方式将影响使用模拟测试的容易程度。在您的示例中,您做出了一些对您不利的设计决策。其中包括:
[ASAppProfileService sharedInstance]
)[MELocationUtils isLastAccurateUploadWithinThresold]
)众所周知,单身人士和班级方法难以嘲笑。如果您对Objective-C的一些更加模糊的语言特性非常了解,那么可以做到这一点,但通常更好的方法是重新设计您的类,以便更容易测试。以下是有关如何执行此操作的一些建议:
使用单例时,在类上定义属性(@property
),测试代码可以使用这些属性在测试期间用模拟替换单例。有很多方法可以实现这一点,但这是我经常使用的一种方法......
在您的标题文件中(例如“MyThing.h”)
@interface MyThing : NSObject
@property (strong, nonatomic) ASAppProfileService *profileService;
@end
在您的实施文件中(例如“MyThing.m”)
@implementation MyThing
- (id)init
{
if (self = [super init]) {
// Initialize with singleton by default.
_profileService = [ASAppProfileService sharedInstance];
}
return self;
}
- (BOOL)someMethod
{
CLLocation *lastKnownLocation = [self.profileService getStoredLastKnownLocation];
…
}
@end
在您的单元测试中:
- (void)test_someMethod
{
id mockProfileService = [OCMockObject mockForClass:[ASAppProfileService class]];
/*
TODO: Use [mockProfileService stub] to stub a return value
for the getStoredLastKnownLocation method.
*/
MyThing *thing = [[MyThing alloc] init];
// Replace the default value of profileService with our new mock.
thing.pofileService = mockProfileService;
// Exercise your object under test.
BOOL result = [thing someMethod];
/*TODO: Add your assertions here. */
}
上面说明的方法是一种称为依赖注入的设计模式。我建议你阅读那个设计模式。它在创建易于测试的代码方面非常有用,并且还具有减少类之间耦合的令人愉快的副作用,这使得代码更容易更改并且更能抵御错误。