简体   繁体   English

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

[英]C# Unit Testing - Thread.Sleep(x) - How to Mock the System Clock

I have to test a method which does a certain amount of work after an interval. 我必须测试一个间隔后做一定工作量的方法。

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

Interval is passed in as a parameter to the class so I can just pass in 0 or 1 but I was interested as to how to mock the system clock if this wasn't the case. Interval作为参数传递给类,所以我可以传入0或1,但我对如何模拟系统时钟感兴趣,如果不是这样的话。

In my test I'd like to be able to simply set the time forward by TimeSpan Interval and have the thread wake up. 在我的测试中,我希望能够通过TimeSpan Interval简单地设置时间并让线程唤醒。

I've never written tests for code which acts upon the executing thread before and I'm sure there are some pitfalls to avoid - please feel free to elaborate on what approach you use. 我从来没有为之前对执行线程起作用的代码编写测试,我确信有一些缺陷要避免 - 请随意详细说明你使用的方法。

Thanks! 谢谢!

If you do not wish to test the fact that the thread actually sleeps, a more straightforward approach (and one that is possible) is to have an ISleepService. 如果您不希望测试线程实际休眠的事实,那么更直接的方法(也是可能的方法)就是拥有一个ISleepService。 You can then mock this out, and then not sleep in your tests, but have an implementation that does cause a Thread.Sleep in your production code. 然后你可以模拟它,然后不在你的测试中睡觉,但有一个实现确实会导致生产代码中的Thread.Sleep。

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

..

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

Example using Moq: 使用Moq的示例:

    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;
            }
        }
    }

Update 更新

On a design\\maintenance note, if it is painful to change the constructor of "SomeClass", or to add Dependency Injection points to the user of the class, then a service locator type pattern can help out here, eg: 在设计\\维护说明中,如果更改“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;
        }
    }
}

You can't really mock the system clock. 你无法真正模拟系统时钟。

If you need to be able to alter the suspend behavior of code like this, you will need to refactor it so that you are not calling Thread.Sleep() directly. 如果你需要能够改变这样的代码的挂起行为,你需要重构它,这样你就不会直接调用Thread.Sleep()

I would create a singleton service, which could be injected into the application when it's under test. 我会创建一个单例服务,它可以在测试时注入应用程序。 The singleton service would have to include methods to allow some external caller (like a unit test) to be able to cancel a sleep operation. 单例服务必须包括允许某些外部调用者(如单元测试)能够取消睡眠操作的方法。

Alternatively, you could use a Mutex or WaitHandle object's WaitOne() method which has a timeout parameter. 或者,您可以使用具有超时参数的MutexWaitHandle对象的WaitOne()方法。 This way you could trigger the mutex to cancel the "sleep" or let it timeout: 这样你可以触发互斥锁取消“睡眠”或让它超时:

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...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM