0

非常に多くの「if 条件」があり、条件が配列数をチェックしている複雑なメソッドに対して、OCMock を使用して単体テストを作成する方法。これが私のサンプルメソッドです:

- (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

お気づきのように、このメソッドの出力を制御するさまざまな条件が多数あります。いくつかのテストを作成する計画を立てる必要があります (条件ごとに 1 つのテストを作成します)。

私の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シングルトンを使用する場合は、テスト コードで使用できるプロパティ ( ) をクラスに定義して、テスト中にシングルトンをモックに置き換えます。これを実装するには多くの方法がありますが、私がよく使用する 1 つのアプローチを次に示します...

ヘッダー ファイル (例: "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 に答える