我正在试图找出对这个类进行单元测试的最佳方法:
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()!
:)
答案 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
而失眠。