2013-07-13 35 views
0

如何使用OCMock編寫一個複雜的方法,它有很多「如果條件」和contions正在檢查數組計數的單元測試。這裏是我的示例方法:使用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; 
} 

如何爲這些類型的方法編寫單元測試?

回答

2

要添加到@Ben_Flynn的回答中,您設計課程的方式將影響使用mock進行測試的容易程度。在你的例子中,你做出了一些對你不利的設計決定。這些措施包括:

  • 使用許多直接引用單身(例[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. */ 
} 

上面說明的方法是稱爲依賴注入的設計模式。我建議你閱讀這種設計模式。它對於創建易於測試的代碼非常有幫助,並且還具有減少類之間的耦合的令人愉快的副作用,這使您的代碼更易於更改並且更易於防禦錯誤。

2

正如你所看到的,有很多不同的條件來管理這種方法的輸出。你需要計劃編寫幾個測試(我會爲每個條件寫一個測試)。

在我setup方法我會創造嘲笑所有你呼喚服務的:在每個測試設置起來的

ASAppProfileService 
CRMgr 
MELocationUtils 
MEProtocolHelper 

這些嘲笑就會得心應手每個測試,所以沒有任何意義基礎。

你也可以考慮在實例或類方法包裝這些服務電話:

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

這將可能使嘲諷容易。

但是,最終沒有捷徑來測試每個可能的輸入集並驗證輸出。