如何编写依赖于文件系统事件的单元测试?

时间:2012-08-30 20:13:09

标签: c# events filesystemwatcher

我有以下代码要测试:

public class DirectoryProcessor
{
    public string DirectoryPath
    {
        get;
        set;
    }

    private FileSystemWatcher watcher;

    public event EventHandler<SourceEventArgs> SourceFileChanged;

    protected virtual void OnSourceFileChanged(SourceEventArgs e)
    {
        EventHandler<SourceEventArgs> handler = SourceFileChanged;
        if(handler != null)
        {
            handler(this, e);
        }
    }

    public DirectoryProcessor(string directoryPath)
    {
        this.DirectoryPath = directoryPath;
        this.watcher = new FileSystemWatcher(directoryPath);
        this.watcher.Created += new FileSystemEventHandler(Created);
    }

    void Created(object sender, FileSystemEventArgs e)
    {
        // process the newly created file
        // then raise my own event indicating that processing is done
        OnSourceFileChanged(new SourceEventArgs(e.Name));
    }
}

基本上,我想编写一个NUnit测试,它将执行以下操作:

  1. 创建目录
  2. 设置DirectoryProcessor
  3. 将一些文件写入目录(通过File.WriteAllText()
  4. 检查DirectoryProcessor.SourceFileChanged是否针对在步骤3中添加的每个文件触发了一次。
  5. 我尝试在第3步之后添加Thread.Sleep(),但很难让超时正确。它正确处理我写入目录的第一个文件,但不是第二个(并且超时设置为60秒)。即使我能以这种方式工作,这似乎是编写测试的一种可怕方式。

    有没有人能很好地解决这个问题?

2 个答案:

答案 0 :(得分:2)

通常,您关心的是测试与文件系统的交互,而不需要测试实际执行操作的框架类和方法。

如果在类中引入了一个抽象层,则可以在单元测试中模拟文件系统,以验证交互是否正确,而无需实际操作文件系统。

在测试之外,“真正的”实现会调用这些框架方法来完成工作。

是的,从理论上讲,您需要对“真实”实施进行集成测试,但实际上它应该是低风险的,不会有太大的变化,并且可以通过几分钟的手动测试进行验证。如果您使用开源文件系统包装器,它可能包括这些测试,让您高枕无忧。

请参阅How do you mock out the file system in C# for unit testing?

答案 1 :(得分:0)

如果您要测试另一个使用此类的对象,我的答案是无关紧要的。

当我将单元测试写入操作时,我更喜欢使用ManualResetEvent

单元测试将类似于:

     ...
     DirectoryProcessor.SourceFileChanged+=onChanged;
     manualResetEvent.Reset();
     File.WriteAllText();
     var actual = manualResetEvent.WaitOne(MaxTimeout);
     ...

当manualResetEvent是ManualResetEvent并且MaxTimeout是一些TimeSpan时(我的建议总是使用超时)。 现在我们错过了“onChanged”:

     private void onChanged(object sender, SourceEventArgs e)
     {
          manualResetEvent.Set();
     }    

我希望这很有用