2012-03-30 54 views
8

正常的解決方案是將其隱藏在接口後面。如何在單元測試中模擬DateTime.Now?

public class RecordService 
{ 
    private readonly ISystemTime systemTime; 

    public RecordService(ISystemTime systemTime) 
    { 
     this.systemTime = systemTime; 
    } 

    public void RouteRecord(Record record) 
    { 
     if (record.Created < 
      systemTime.CurrentTime().AddMonths(-2)) 
     { 
      // process old record 
     } 

     // process the record 
    } 
} 

在單元測試中,你可以使用模擬對象,並決定什麼返回

[TestClass] 
public class When_old_record_is_processed 
{ 
    [TestMethod] 
    public void Then_it_is_moved_into_old_records_folder() 
    { 
     var systemTime = A.Fake<ISystemTime>(); 
     A.CallTo(() => system.Time.CurrentTime()) 
      .Returns(DateTime.Now.AddYears(-1)); 

     var record = new Record(DateTime.Now); 
     var service = new RecordService(systemTime); 

     service.RouteRecord(record); 

     // Asserts... 
    } 
} 

我不喜歡注入另一個接口到我的課堂只是爲了獲得當前的時間。對於這樣一個小問題,感覺過於沉重。解決方案是使用靜態類與公共功能。

public static class SystemTime 
{ 
    public static Func<DateTime> Now =() => DateTime.Now; 
} 

現在我們可以刪除ISystemTime注射RecordService看起來像這樣

public class RecordService 
{ 
    public void RouteRecord(Record record) 
    { 
     if (record.Created < SystemTime.Now.AddMonths(-2)) 
     { 
      // process old record 
     } 

    // process the record 
    } 
} 

在單元測試中,我們可以很容易地嘲笑系統時間。

[TestClass] 
public class When_old_record_is_processed 
{ 
    [TestMethod] 
    public void Then_it_is_moved_into_old_records_folder() 
    { 
     SystemTime.Now =() => DateTime.Now.AddYears(-1); 
     var record = new Record(DateTime.Now); 
     var service = new RecordService(); 

     service.RouteRecord(record); 

     // Asserts... 
    } 
} 

當然,這一切都有一個缺點。您正在使用公共字段(The HORROR!),因此沒有人阻止您編寫這樣的代碼。

public class RecordService 
{ 
    public void RouteRecord(Record record) 
    { 
     SystemTime.Now =() => DateTime.Now.AddYears(10); 
    } 
} 

另外我認爲,教育開發人員比創建抽象是爲了保護他們免於犯錯。其他可能的問題與運行測試有關。如果您忘記將功能恢復到原始狀態,則可能會影響其他測試。這取決於單元測試運行器執行測試的方式。 您可以使用相同的邏輯來嘲笑文件系統操作

public static class FileSystem 
{ 
    public static Action<string, string> MoveFile = File.Move; 
} 

在實施這種利用公共職能的功能(嘲諷的時候,簡單的文件系統操作),我的意見是完全可以接受的。它使得代碼更易於閱讀,減少了依賴性,並且很容易在單元測試中模擬。

+1

你用過moq嗎? (或任何其他模擬框架)http://code.google.com/p/moq/ – Magrangs 2012-03-30 11:38:04

+0

我修正了您的代碼的格式。請將它與您的版本進行比較,以瞭解如何正確執行此操作。 – 2012-03-30 11:41:04

+1

@Magrangs:與所有其他「正常」嘲諷框架一樣,Moq不能嘲諷像「DateTime.Now」這樣的靜態方法和屬性。 – 2012-03-30 11:42:07

回答

3

你不需要手動實現這個。你可以使用Moles框架來做到這一點。 channel 9

+2

和moq ..和許多其他嘲諷框架:-) – Magrangs 2012-03-30 11:39:34

+0

事實上,這個問題讓我感到驚訝,因爲視頻中痣的第一個例子是如何輕鬆地嘲笑DateTime.Now :) – daryal 2012-03-30 11:42:15

+1

@Magrangs:不正確。請參閱我對上述其他評論的回答。 – 2012-03-30 11:42:37

3

我不會說現在的時間是如此之小,如果您的應用程序變得國際化,並且在多個時區中使用,哪個時區是當前時間,最有可能您想要一個時區常用的時區使用,無論您的區域設置?

該接口允許您將這些知識抽象出來;我會考慮將「當前」時間作爲一項相當複雜的任務來檢索。

0

考慮到我們談論的是慣用的c#,我不太理解接口背後儀式的問題。從本質上講,您有一個TimeProvider,您將其作爲依賴項注入到構造函數中,併爲它提供了一個存根,以驅動相關測試代碼的邏輯。

我不認爲你的其他方法提供了好處,它們具有消極方面的不習慣性以及消除依賴注入的有用方面(記錄依賴關係,嘲諷,控制反轉等)

相關問題