2012-02-18 55 views
3

有一條規則說你應該如何模擬你在Objective C/Cocoa中沒有的類? (例如NSDate)

只有您擁有的模擬對象。

我想我理解這個原因 - 框架提供的模擬類可能會導致奇怪的行爲。

有什麼選擇?

當你需要使用NSDate的虛擬日期時呢?

在過去,我將日期方法從NSDate調到我自己的類--NSDateMock - 但有些事情告訴我這是錯誤的!

One Solution - Wrapper?

創建一個NSDate的包裝,但你必須實現所有的方法。

或者你會實施你正在使用的?這似乎是一個混亂的做法。

我的問題

什麼是你不擁有如NSDate的嘲諷類的好辦法嗎?

更新1

我發現this article on mocking這似乎意味着,寫一個瘦包裝是要走的路。我不太清楚爲什麼,但我覺得這是一個黑客。再次,它可以使代碼更具表現力。

但是這引發了問題,在NSDate的情況下,你是否需要將包裝類注入每個需要知道日期的類?當然不是......

更新2

已經有關於這個問題的一些很好的答案,但我仍然堅持尋找其他的答案 - 必須有這樣的明確的方式,肯定?我仍然沒有看到類別如何給我一個我可以控制的虛擬對象。

+1

只是好奇 - 爲什麼你想創建模擬日期?如果不瞭解你的設置,我的第一個想法是想知道爲什麼你需要首先嘲笑經過充分測試的系統課程。 – 2012-02-18 22:05:44

+0

@Tim如果一個應用程序正在處理時間敏感的操作,我可能想要模擬發生的事情,比如接近午夜。我將如何實現這一目標? – 2012-02-19 00:47:55

回答

3

一般來說,這是一個「如何模擬靜態/課堂級別的方法」的問題,Google搜索顯示了很多不同的想法,所以主要歸結爲味道。

我認爲部分模擬是一種代碼氣味,因爲它表明您的類正在測試(CUT)不可測試 - >重構時間。

我已經在過去的2種方式解決了這個:

1)傳遞一個DateProvider(這是一個接口)到CUT

interface DateProvider 
    date timenow() 
end 

的構造器在測試時這是您的MockDateProvider,您可以從您的測試類更改狀態。我可能會使用一個公共的靜態字段我可以在我的測試中改變

class MockDateProvider :: DateProvider 
    public static field fakeDate 
    date timenow() 
     return fakedate 
    end 
end 

在它只是你使用的日期創建方法的實際系統。

class RealDateProvider :: DateProvider 
    date timenow() 
     return LibraryDateMaker.newDate() 
    end 
end 

我可能使2層構造,一種採用該接口而另一個使用RealDateProvider沒有任何生產需要的類對象中傳遞。

這是最好的OO做事方式。我認爲!

2.)讓你自己的靜態日期提供者,你可以重寫的行爲。

而不是LibraryDateMaker.newDate()你做一個ConfigurableDateMaker.newDate()靜態。它使用與1相同的對象,但有一個設置器允許您根據需要將行爲更改爲模擬提供程序。默認爲真實。

這樣做的好處是你不必將任何東西傳遞給你的構造函數,並且你可以繼續使用靜態方法進行非常常見的活動。

簡言之,CUT調用ConfigurableDateMaker.newDate()。默認情況下會返回一個真實的日期,但在測試類中,您可以設置行爲以在調用CUT之前使用模擬。

class ConfigurableDateMaker 
    public static DateProvider provider 
    static date timenow() 
     return provider.newDate() 
    end 
    // add the 2 provider classes as inner classes in here 
end 

希望這有一定的道理。

1

您可以改爲創建NSDate一個類別,如

[NSDate mockDate]; 

沒有必要創建一個全新的類來實現的另一種方法。

+0

然後...調整模擬的真實日期方法?但是,我將如何指定模擬日期應該返回的內容 - 類別不能有實例變量... – 2012-02-19 01:33:07

+0

似乎很髒,但是您可以在任何地方使用自己的[NSDate mockDate],並且測試用例只是返回您自己的日期。在所有其他情況下,它應該簡單地返回'[NSDate date]' – fscheidl 2012-02-19 02:40:32

+0

我不明白這將如何工作。 [NSDate mockDate]在任何地方都意味着我仍然不得不交換一個返回我自己的日期的方法,這意味着我仍然必須調整(我試圖避免) - 肯定會增加額外的複雜性只是調整真正的日期方法而沒有真正的回報?加上我的代碼將散佈與[NSDate mockDate]這是混亂! – 2012-02-19 13:25:45

1

你試圖通過嘲笑NSDate解決什麼具體問題?一般來說,不應該模擬像NSDate這樣的值對象。它的界面提供了簡單的方法來創建一個代表任何給定日期/時間的對象。

您可以在您的測試類中創建一個方便的方法生成的日期,你需要:

-(NSDate *)dateFromString:(NSString *)dateString { 
    // short style is 2/20/12 10:05 AM 
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 
    formatter.dateStyle = NSDateFormatterShortStyle; 
    formatter.timeStyle = NSDateFormatterShortStyle; 
    return [formatter dateFromString:dateString]; 
} 

-(void)testTimer { 
    NSDate *startTime = [self dateFromString:@"2/20/12 11:58 PM"]; 
    NSDate *endTime = [self dateFromString:@"2/21/12 12:02 AM"]; 
    // pass dates into your timer method 
} 

另外,如果你需要測試直接獲取當前時間的方法,你可以提供一個瘦包裝各地[NSDate date]

-(NSDate *)now { 
    return [NSDate date]; 
} 

-(void)startTimer { 
    self.startTime = [self now]; 
} 

-(void)stopTimer { 
    NSDate *endTime = [self now]; 
    // ... do something 
} 

然後,在您的測試,你嘲笑你的瘦包裝與部分模擬:

-(void)testStopTimer { 
    Timer *timer = [[Timer alloc] init]; 
    timer.startTime = [self dateFromString:@"2/20/12 11:58 PM"]; 

    id mockTimer = [OCMockObject partialMockForObject:timer]; 
    [[[mockTimer stub] andReturn:[self dateFromString:@"2/21/12 12:01 AM"] now]; 

    [timer stopTimer]; 

    // verify expected behavior... 
} 
+0

從我的例子應用程序:我有用戶花費在文檔上的日誌條目。計時器類具有啓動和停止方法。我想模擬在午夜之前啓動計時器時發生的情況,並在測試後立即停止計時。還有更多潛在的例子 - 任何一類的行爲取決於當前的時間。 – 2012-02-19 09:46:05

+0

增加了一些示例代碼。 – 2012-02-20 18:33:54

+0

但是這取決於被注入時間的待測方法。所以我想你的解決方案是「傳入的對象是你不擁有的類的實例」 – 2012-02-21 00:36:12

1

我不知道,如果這得到什麼你正在嘗試做的,但是當我碰到類似的情況我已經做了類似如下:

  1. 每當我有一個類,取決於我可能想稍後模擬出的一些類, 我在我的類的一個方法中封裝了對該類的訪問。例如,我將調用[NSDate date]來獲取當前時間,而不是調用 ,而是實現一種名爲currentTime的方法。 該方法的實現將簡單地返回[NSDate時間]。

  2. 我執行我的測試針對從我的對象創建「部分模擬」測試物, 捻熄currentTime的方法返回我的首選測試值,而不是讓 從得到調用真正的currentTime的實現。

我使用OCMock框架做這一切,所以它看起來是這樣的:

NSDate *testDate = // put whatever you want here 
id mockTestObject = [OCMock partialMockForObject:testObject]; 
[mockTestObject stub] andReturn:testDate] currentDate]; 

[mockTestObject doSomethingThatUsersCurrentTime]; 
[mockTestObject verify]; 

OCMock提供了很多不同的變種,對捻熄,我發現它工作得很好對於這種事情。