0

如何使用 OCMock 为具有如此多“if 条件”的复杂方法编写单元测试,并且 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;
}

如何为这些方法编写单元测试?

4

2 回答 2

2

正如您所注意到的,有很多不同的条件控制着这个方法的输出。您需要计划编写几个测试(我会为每个条件编写一个测试)。

在我的setup方法中,我将为您调用的所有服务创建模拟:

ASAppProfileService
CRMgr
MELocationUtils
MEProtocolHelper

这些模拟在每个测试中都很方便,因此在每个测试的基础上设置它们是没有意义的。

您还可以考虑将这些服务包装在实例或类方法调用中:

+ (BOOL)isCheckedInStatusChange
{
    BOOL checkedInStatusChange = NO;
    if([[CRMgr getSharedInstance] isCREnabled])
    {
        checkedInStatusChange = [[CRMgr getSharedInstance] orchestrateCROperationsWithLocation:newLocation];
    }
    return checkedInStatusChange;
}

那会让嘲笑更容易。

但最终,测试每组可能的输入并验证输出并没有捷径可走。

于 2013-07-15T19:25:16.850 回答
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. */
}

上面说明的方法是一种称为依赖注入的设计模式。我建议您阅读该设计模式。它在创建易于测试的代码方面非常有帮助,并且还具有减少类之间耦合的令人愉快的副作用,这使您的代码更容易更改并且更能抵抗错误。

于 2013-07-18T15:54:12.770 回答