如何用C#模拟文件系统进行单元测试?

如何用C#模拟文件系统进行单元测试?,c#,unit-testing,mocking,C#,Unit Testing,Mocking,是否有任何库或方法可以模拟C#中的文件系统来编写单元测试?在我目前的例子中,我有一些方法可以检查某个文件是否存在并读取创建日期。将来我可能需要更多的东西。我不确定您将如何模拟文件系统。您可以做的是编写一个测试夹具设置,创建一个文件夹等,其中包含测试所需的结构。测试运行后,teardown方法将对其进行清理 编辑添加:在考虑这一点时,我认为您不想模拟文件系统来测试这种类型的方法。如果您模拟文件系统,使其在某个文件存在时返回true,并在测试检查该文件是否存在的方法时使用该值,那么您就不会测试任何内

是否有任何库或方法可以模拟C#中的文件系统来编写单元测试?在我目前的例子中,我有一些方法可以检查某个文件是否存在并读取创建日期。将来我可能需要更多的东西。

我不确定您将如何模拟文件系统。您可以做的是编写一个测试夹具设置,创建一个文件夹等,其中包含测试所需的结构。测试运行后,teardown方法将对其进行清理


编辑添加:在考虑这一点时,我认为您不想模拟文件系统来测试这种类型的方法。如果您模拟文件系统,使其在某个文件存在时返回true,并在测试检查该文件是否存在的方法时使用该值,那么您就不会测试任何内容。如果您想测试一个依赖于文件系统的方法,但文件系统活动不是测试中的方法的一部分,那么模拟文件系统将非常有用。

您可能需要构建一个契约来定义您需要从文件系统中获得的东西,然后围绕这些东西编写一个包装器功能。在这一点上,您将能够模拟或删除实现

例如:

interface IFileWrapper { bool Exists(String filePath); }

class FileWrapper: IFileWrapper
{
    bool Exists(String filePath) { return File.Exists(filePath); }        
}

class FileWrapperStub: IFileWrapper
{
    bool Exists(String filePath) 
    { return (filePath == @"C:\myfilerocks.txt"); }
}

在测试中模拟文件系统是很困难的,因为.NET文件API并不是真正基于可以模拟的接口或可扩展类

但是,如果您有自己的功能层来访问文件系统,您可以在单元测试中模拟它


作为嘲弄的一种选择,考虑只需创建作为测试设置的一部分所需的文件夹和文件,并在拆卸方法中删除它们。

< P>编辑:安装NuGET包。

最初接受此答案时,此软件包不存在。原始答案针对以下历史背景提供:

您可以通过创建一个接口来实现:

interface IFileSystem {
    bool FileExists(string fileName);
    DateTime GetCreationDate(string fileName);
}
以及创建一个使用 System.IO.File.Exists()等。然后可以使用 模拟框架;我推荐

编辑:有人做了这件事,并把它贴到了网上

我使用这种方法在IClock中模拟DateTime.UtcNow 接口(对我们的测试非常有用,可以控制 时间流!),以及更传统的ISqlDataAccess 接口

另一种方法可能是使用,这允许您 截获对类的调用并将它们剔除。然而,这确实需要成本 并且需要安装在整个团队的PC和 为了运行,您的构建服务器显然也无法运行 System.IO.File,如所示

您也可以接受某些方法是不可单元测试的 并在单独的慢速运行集成/系统测试中对其进行测试 套房


我同意杰米·艾德的回答。不要试图嘲笑你没有写的东西。将有各种各样的依赖关系,你不知道-密封类,非虚拟方法等


另一种方法是用可模仿的东西包装appopiate方法。e、 g.创建一个名为FileWrapper的类,该类允许访问文件方法,但您可以模拟它。

回答您的特定问题:不,没有库允许您模拟文件I/O调用(据我所知)。这意味着“正确地”单元测试您的类型将要求您在定义类型时考虑此限制

关于如何定义“适当”单元测试的简要说明。我认为,单元测试应该确认您获得了提供已知输入的预期输出(异常、方法调用等)。这允许您将单元测试条件设置为一组输入和/或输入状态。我发现最好的方法是使用基于接口的服务和依赖项注入,以便通过构造函数或属性传递的接口提供类型外部的每个职责

所以,记住这一点,回到你的问题上来。我通过创建
IFileSystemService
接口以及
FileSystemService
实现模拟了文件系统调用,该实现只是mscorlib文件系统方法的一个门面。然后,我的代码使用的是
IFileSystemService
,而不是mscorlib类型。这允许我在应用程序运行时插入标准的
FileSystemService
,或者在单元测试中模拟
IFileSystemService
。无论如何运行,应用程序代码都是相同的,但底层基础结构允许轻松测试代码


我承认在mscorlib文件系统对象周围使用包装器是一件痛苦的事情,但是,在这些特定场景中,由于测试变得如此简单和可靠,因此额外的工作是值得的。

我们目前使用一个专有的数据引擎,其API没有作为接口公开,因此我们很难对数据访问代码进行单元测试。然后我也采用了Matt和Joseph的方法。

创建一个接口并模拟它进行测试是最干净的方法。但是,作为替代方案,您可以查看一下框架。

我遇到了以下解决方案:

  • 编写集成测试,而不是单元测试。为了实现这一点,您需要一种简单的方法来创建一个文件夹,您可以在其中转储内容,而无需担心其他测试的干扰。我有一个简单的类,它可以创建一个唯一的每个测试方法文件夹来使用
  • 编写一个可模拟的System.IO.File。也就是说,创建一个。我发现使用这种方法通常会导致测试结果,这些测试只是证明您可以编写模拟语句,但在IO使用量很小的情况下确实可以使用它
  • 检查抽象层,并从类中提取文件IO。用户可以为此创建一个接口。其余部分使用集成测试(但这将非常小)。这与上面的不同之处在于,它不是执行文件。请阅读
    using Microsoft.QualityTools.Testing.Fakes;
    ...
    using (ShimsContext.Create())
    {
         System.IO.Fakes.ShimFile.ExistsString = (p) => true;
         System.IO.Fakes.ShimFile.ReadAllTextString = (p) => "your file content";
    
          //Your methods to test
    }
    
    public class ManageFile {
       private readonly IFileSystem _fileSystem;
       public ManageFile(IFileSystem fileSystem){
    
          _fileSystem = fileSystem;
       }
    
       public bool FileExists(string filePath){}
           if(_fileSystem.File.Exists(filePath){
              return true;
           }
           return false;
       }
    }
    
    var mockFileSysteme = new MockFileSystem();
    var mockFileData = new MockFileData("File content");
    mockFileSysteme.AddFile(mockFilePath, mockFileData );
    var manageFile = new ManageFile(mockFileSysteme);