C#单元测试 - Thread.Sleep(x) - 如何模拟系统时钟

问题描述:

我必须测试一个方法,在间隔后执行一定量的工作。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

如果你不想测试线程真正睡眠的事实,更直接的方法(和可能的方法)是有一个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

你不能真正嘲笑系统时钟。

如果您需要能够改变这种代码的暂停行为,您需要重构它,以便您不直接调用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