单元测试FileSystemWatcher:如何编程触发更改事件?

11

我有一个FileSystemWatcher正在监视一个目录的变化,当有新的XML文件出现时,它会解析该文件并对其执行某些操作。

我在项目中有一些样本XML文件,我正在使用它们来进行我编写的解析器的单元测试。

我正在寻找一种方法,可以使用这些样本XML文件来测试FileSystemWatcher

是否有可能通过编程方式创建一个事件(与XML文件有关)以触发FSW.Changed事件?


你不能打开文件并向XML文件添加一些测试吗?例如,添加一个注释,然后将其保存回磁盘。 - Don
请分享您要测试和单元测试尝试的 FSW 代码。 - Yogi
1
首先,您可以为FileSystemWatcher创建一个模拟对象,并将其传递给您的类,然后调用模拟的更改事件。另一种选择是创建一个派生自FileSystemWatcher的模拟类,并根据需要覆盖更改方法,并将其传递给您的类进行测试。还有一种选择,在您的测试中,当您的代码正在监视文件更改时,可以在并行任务中复制和粘贴真实文件到您的目录中。 - mehmet mecek
@mecek 你如何调用模拟对象的变更事件? - Tigerware
3个回答

15

我认为你在这里采取了错误的方法。

你不应该尝试直接对FileSystemWatcher类进行单元测试(你无法控制它!)。相反,你可以尝试以下方法:

1) 为FileSystemWatcher类编写一个包装器类,它将其功能委托给一个FileSystemWatcher实例。这里有一个示例,其中有一个方法和一个事件,请根据需要添加更多成员:

public class FileSystemWatcherWrapper
{
    private readonly FileSystemWatcher watcher;

    public event FileSystemEventHandler Changed;

    public FileSystemWatcherWrapper(FileSystemWatcher watcher)
    {
        this.watcher = watcher
        watcher.Changed += this.Changed;
    }

    public bool EnableRaisingEvents
    {
        get { return watcher.EnableRaisingEvents; }
        set { watcher.EnableRaisingEvents = value; }
    }
}
(Note how the instance of FileSystemWatcher is passed to the class constructor; you could create a new instance on the fly instead when the wrapper is constructed)
2) 为该类提取一个接口:
public interface IFileSystemWatcherWrapper
{
    event FileSystemEventHandler Changed;
    bool EnableRaisingEvents { get; set; }
}

//and therefore...

public class FileSystemWatcherWrapper : IFileSystemWatcherWrapper

3) 让你的类依赖于接口:

public class TheClassThatActsOnFilesystemChanges
{
    private readonly IFileSystemWatcherWrapper fileSystemWatcher;

    public TheClassThatActsOnFilesystemChanges(IFileSystemWatcherWrapper fileSystemWatcher)
    {
        this.fileSystemWatcher = fileSystemWatcher;

        fileSystemWatcher.Changed += (sender, args) =>
        {
            //Do something...
        };
    }
}

4) 在应用程序初始化时,使用任何依赖注入引擎实例化您的类,或只需进行“贫民注入”:

var theClass = new TheClassThatActsOnFilesystemChanges(
    new FileSystemWatcherWrapper(new FileSystemWatcher()));

5) 现在开始编写 TheClassThatActsOnFilesystemChanges 的单元测试,通过创建一个模拟的 IFileSystemWatcherWrapper 并随意触发事件!您可以使用任何模拟引擎,例如Moq

核心要点:

当您依赖于一个无法控制或无法进行有意义单元测试的类时,请编写一个适当接口的包装器,并依赖于该接口。尽管您的包装器非常轻巧,如果不能进行单元测试,也不会对其产生太大影响,而客户端类现在可以得到适当的单元测试。


2
FSW 是一个难以准确模拟的东西。在你的测试计划中一定要包括延迟、重复通知、来自其他软件的锁定和缺失通知。可能还有其他需要注意的地方我没有提到。 - Mitch
1
你在示例中链接事件吗?我似乎无法像那样链接事件。我可能漏掉了什么吗? - TiQP
1
谢谢!请修复一个小错别字 - 在 FileSystemWatcherWrapper 构造函数中添加 "this.watcher = watcher;". - ulmer-morozov
@BluE 你可以为 FileSystemWatcherWrapper 添加公共方法来触发事件。 - Konamiman

3

由于FileSystemWatcher不是密封类,因此您可以从中继承并创建一个接口:

public class FileSystemWatcherWrapper : FileSystemWatcher, IFileSystemWatcherWrapper
    {
      //empty on purpose, doesnt need any code
    }

 public interface IFileSystemWatcherWrapper
    {
        event FileSystemEventHandler Created;
        event FileSystemEventHandler Deleted;
        bool EnableRaisingEvents { get; set; }

        bool IncludeSubdirectories { get; set; }

        string Path { get; set; }
        string Filter { get; set; }

        void Dispose();
    }
}

那么你可以使用Moq进行单元测试。

1
使用System.IO.Abstractions.IFileSystemWatcher接口编写单元测试时,我只需要为DI创建FileSystemWatcherWrapper类,一切都很好。非常简洁和低代码的解决方案。 - Ted

2

只需从 FileSystemWatcher 派生并添加您需要的任何内容到接口中:

public interface IFileSystemWatcherWrapper : IDisposable
{        
    bool EnableRaisingEvents { get; set; }
    event FileSystemEventHandler Changed;
    //...
}

public class FileSystemWatcherWrapper : FileSystemWatcher, IFileSystemWatcherWrapper
{
    public FileSystemWatcherWrapper(string path, string filter)
        : base(path, filter)
    {
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接