单元测试一个线程类,避免测试中的Thread.Sleep()?

时间:2011-07-08 16:28:57

标签: multithreading unit-testing nunit sleep thread-sleep

我正在试图找出对这个类进行单元测试的最佳方法:

public class FileGroupGarbageCollector
{
    private Task _task;

    private readonly AutoResetEvent _event = new AutoResetEvent(false);

    public void Start()
    {
        _task = Task.Factory.StartNew(StartCollecting);

    }

    public void Stop()
    {
        _event.Set();
    }

    private void StartCollecting()
    {
        do
        {
            Process();
        }
        while (!_event.WaitOne(60000, false));            
    }

    private void Process()
    {
        /* do some work to the database and file system */
    }
}

它不应该是最好的课程,只是想弄清楚什么!

然后我有一个单元测试,我想要启动然后停止服务,声明私有'Processs'方法对数据库或文件系统做了一些事情。

我的单元测试如下(nunit):

    [Test]
    public void TestStart()
    {
        var fg = new FileGroupGarbageCollector(30000);

        fg.Start();

        Thread.Sleep(5000); // i hate this!

        fg.Stop();

        // assert it did what i wanted it to do!
    }

有没有办法或任何好的模式可以在这里使用,所以我可以避免Thread.Sleep()?我讨厌睡在单元测试中的想法(更不用说在生产代码中),但我拒绝只测试私有功能!我想测试这个类的公共接口。

非常感谢任何答案:)

更新后更新

我采用了IoC的方式,它的效果非常好:)

  

公共接口IEventFactory       {           IEvent Create();       }

public interface IEvent
{
    bool WaitOne(int timeout);
    void Set();
}

然后我的模拟对象(使用Moq):

 var mockEvent = new Mock<IEvent>();
 var mockEventFactory = new Mock<IEventFactory>();

 mockEvent.Setup(x => x.WaitOne(It.IsAny<int>())).Returns(true);
 mockEvent.Setup(x => x.Set());

 mockEventFactory.Setup(x => x.Create()).Returns(mockEvent.Object);

因此,立即调用IEvent.WaitOne()会返回true并退出,因此不需要Thread.Sleep()!

:)

2 个答案:

答案 0 :(得分:7)

基本上,您必须在此处应用反转控制模式。因为代码是高度耦合的,所以测试它时遇到问题。

您应该明确区分所有实体并将它们放在相应的接口上。如果通过接口使用实体,则很容易模拟它。

public interface ITaskFactory {}
public interface IThreadManager {}
public interface ICollectorDatabase {}
public interface IEventFactory {} 

public class FileGroupGarbageCollector 
{
  ITaskFactory taskFactory;
  IThreadManager threadManager;
  ICollectorDatabase database;
  IEventFactory events;

  FileGroupGarbageCollector (ITaskFactory taskFactory,
    IThreadManager threadManager, ICollectorDatabase database,
    IEventFactory events)
  {
     // init members..
  }
}

只要所有依赖项都被隔离,FileGroupGarbageCollector就不会直接使用它们中的任何一个。在您的测试中,IEventFactory mock将返回Event,如果调用WaitOne方法则不会执行任何操作。因此,您的代码中不需要任何睡眠。

尽可能多地找到 - 模拟,控制反转,依赖注入模式。

答案 1 :(得分:1)

Thread.Sleep是设计糟糕的计划的标志。但是,它在单元测试中非常有用。

唯一的另一种选择是改变“时间”的用法。 Rx团队在这方面做了一些很棒的工作; their schedulers are all testable。但这并没有帮助您的特定情况(除非您转换为Rx调度程序)。

如果您真的想在单元测试中避免使用Thread.Sleep,那么您需要抽象出依赖于时间的部分(使用Inversion of Control或拦截库,如Microsoft Moles);问题在于,很难创建一个完整而一致的“时间”抽象。就个人而言,我在单元测试中没有因Thread.Sleep而失眠。