5

我正在尝试找出对此类进行单元测试的最佳方法:

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 */
    }
}

它不应该是最完善的课程,只是想弄清楚一些事情!

然后我有一个单元测试,我想在其中启动然后停止服务,断言私有“进程”方法对数据库或文件系统做了一些事情。

我的单元测试如下(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()!

:)

4

2 回答 2

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 模拟将返回 Event,如果调用 WaitOne 方法,它将什么都不做。因此,您的代码中不需要任何睡眠。

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

于 2011-07-08T16:44:02.133 回答
2

Thread.Sleep是设计不佳程序的标志。但是,它在单元测试中非常有用。

唯一的其他选择是更改“时间”的用法。Rx 团队在这方面做了一些很棒的工作;他们的调度程序都是可测试的。但这无助于您的特定情况(除非您转换为 Rx 调度程序)。

如果您真的想Thread.Sleep在单元测试中避免,那么您需要抽象出依赖时间的部分(使用控制反转或像 Microsoft Moles 这样的拦截库);问题是很难创建完整且一致的“时间”抽象。Thread.Sleep就个人而言,我不会因为参加单元测试而失眠。

于 2011-07-08T16:52:08.353 回答