2010-08-03 65 views
11

我必須測試一個方法,在間隔後執行一定量的工作。C#單元測試 - Thread.Sleep(x) - 如何模擬系統時鐘

while (running) 
{ 
    ... 
    // Work 
    ... 
    Thread.Sleep(Interval); 
} 

間隔傳過來的參數對類這樣我就可以傳遞0或1,但我很感興趣,至於如何嘲笑系統時鐘,如果這是情況並非如此。

在我的測試中,我希望能夠通過TimeSpan 間隔簡單地設置時間,並讓線程喚醒。

我從來沒有寫過測試代碼的行爲執行線程之前,我敢肯定有一些陷阱,以避免 - 請隨時闡述你使用什麼方法。

謝謝!

+0

實際上改變時鐘值(我確信你不想這樣做)的缺點,你不會能夠獲得你正在尋找的結果。 – 2010-08-03 15:59:20

+2

首選的解決方案是將定時功能與服務隔離,以便您可以嘲笑服務以進行測試。如果你不能這樣做(通常是因爲它是現有的代碼庫),那麼另一種方法是使用像Moles這樣的框架繞開靜態調用:http://research.microsoft.com/en-us/projects/moles/ – 2010-08-03 16:04:19

回答

15

如果你不想測試線程真正睡眠的事實,更直接的方法(和可能的方法)是有一個ISleepService。然後,您可以將它嘲笑出來,然後不會在測試中入睡,但是會有一個確實會導致生產代碼中出現Thread.Sleep的實現。

ISleepService sleepService = Container.Resolve<ISleepService>(); 

.. 

while (running) 
{ 
    ... 
    // Work 
    ... 
    sleepService.Sleep(Interval); 
} 

示例使用起訂量:

public interface ISleepService 
    { 
     void Sleep(int interval); 
    } 

    [Test] 
    public void Test() 
    { 
     const int Interval = 1000; 

     Mock<ISleepService> sleepService = new Mock<ISleepService>(); 
     sleepService.Setup(s => s.Sleep(It.IsAny<int>())); 
     _container.RegisterInstance(sleepService.Object); 

     SomeClass someClass = _container.Resolve<SomeClass>(); 
     someClass.DoSomething(interval: Interval); 

     //Do some asserting. 

     //Optionally assert that sleep service was called 
     sleepService.Verify(s => s.Sleep(Interval)); 
    } 

    private class SomeClass 
    { 
     private readonly ISleepService _sleepService; 

     public SomeClass(IUnityContainer container) 
     { 
      _sleepService = container.Resolve<ISleepService>(); 
     } 

     public void DoSomething(int interval) 
     { 
      while (true) 
      { 
       _sleepService.Sleep(interval); 
       break; 
      } 
     } 
    } 

更新

在設計\保養的注意,如果它是痛苦的改變 「SomeClass的」 的構造,或添加依賴注入指向該類的用戶,然後服務定位器類型模式可以在這裏幫助,例如:

private class SomeClass 
{ 
    private readonly ISleepService _sleepService; 

    public SomeClass() 
    { 
     _sleepService = ServiceLocator.Container.Resolve<ISleepService>(); 
    } 

    public void DoSomething(int interval) 
    { 
     while (true) 
     { 
      _sleepService.Sleep(interval); 
      break; 
     } 
    } 
} 
+0

太棒了 - 這很有意義 - Moq絕對是在路線圖上,所以你回答提供了一個簡單的啓動。 – gav 2010-08-03 17:00:59

2

你不能真正嘲笑系統時鐘。

如果您需要能夠改變這種代碼的暫停行爲,您需要重構它,以便您不直接調用Thread.Sleep()

我會創建一個單例服務,當它處於測試中時,它可以注入到應用程序中。單身服務必須包括允許某些外部呼叫者(如單元測試)能夠取消睡眠操作的方法。

或者,您可以使用具有超時參數的MutexWaitHandle對象的WaitOne()方法。通過這種方式,你可以觸發互斥體取消「睡眠」或讓它超時:

public WaitHandle CancellableSleep = new WaitHandle(); // publicly available 

// in your code under test use this instead of Thread.Sleep()... 
while(running) { 
    // .. work .. 
    CancellableSleep.WaitOne(Interval); // suspends thread for Interval timeout 
} 


// external code can cancel the sleep by doing: 
CancellableSleep.Set(); // trigger the handle... 
+1

WaitHandle是一種非常繁重的方式(在系統資源和性能方面)提供了一種替代'Thread.Sleep()'的方法。我不會推薦一個WaitHandle,除非你有其他的代碼可以提供線程喚醒時的指導。 – 2010-08-03 16:10:40

+1

+1不能模擬系統時鐘。 – Larsenal 2010-08-03 16:27:14

+1

實際上,你可以使用系統調用攔截,但這肯定是矯枉過正。 – 2010-08-03 16:33:00