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

时间:2009-07-06 14:42:48

标签: c# unit-testing mocking

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

14 个答案:

答案 0 :(得分:138)

编辑:安装NuGet包System.IO.Abstractions

最初接受此答案时,此程序包不存在。原始答案是根据以下历史背景提供的:

  

您可以通过创建界面来实现:

interface IFileSystem {
    bool FileExists(string fileName);
    DateTime GetCreationDate(string fileName);
}
     

并创建一个使用的“真实”实现   System.IO.File.Exists()等你可以使用a来模拟这个接口   嘲弄框架;我推荐Moq

     

编辑:有人完成了这项工作,并在网上发布here

     

我已经用这种方法模拟了IClock中的DateTime.UtcNow   接口(对我们的测试真正有用,能够控制   流动的时间!),更传统的是,一个ISqlDataAccess   接口

     

另一种方法可能是使用TypeMock,这可以让你   拦截对类的调用并将其存根。然而这确实是成本   钱,需要安装在整个团队的PC上   你的构建服务器为了运行,它显然也无法运行   System.IO.File,因为它can't stub mscorlib

     

您也可以接受某些方法不可单元测试   并在单独的慢速运行集成/系统测试中测试它们   套件。

答案 1 :(得分:75)

Install-Package System.IO.Abstractions

虚构库现在存在,System.IO.Abstractions有一个NuGet包,它抽象出System.IO命名空间。

还有一组测试助手,System.IO.Abstractions.TestingHelpers - 在撰写本文时 - 仅部分实现,但是一个非常好的起点。

答案 2 :(得分:9)

您可能需要构建一个合同来定义文件系统中需要的内容,然后围绕这些功能编写包装器。那时你就可以模拟或删除实现了。

示例:

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"); }
}

答案 3 :(得分:5)

我的建议是使用http://systemwrapper.codeplex.com/ 因为它为System名称空间

中主要使用的类型提供了包装器

答案 4 :(得分:3)

我遇到了以下解决方案:

  • 编写集成测试,而不是单元测试。为此,您需要一种简单的方法来创建一个文件夹,您可以在其中转储内容而不必担心其他测试会干扰。我有一个简单的TestFolder类,可以创建一个唯一的每个测试方法文件夹来使用。
  • 编写一个可模拟的System.IO.File。那就是创建一个IFile.cs。我发现使用它通常最终会得到一些测试,这些测试只能证明你可以编写模拟语句,但是当IO使用量很小时会使用它。
  • 检查您的抽象层,并从类中提取文件IO。为此创建一个接口。其余的使用集成测试(但这将非常小)。这不同于上面的内容,而不是做文件。你写了意图,比如说ioThingie.loadSettings()
  • System.IO.Abstractions。我还没有使用过它,但它是我最喜欢玩的那个。

我最终使用上面的所有方法,这取决于我写的内容。但是大多数时候,当我编写击中IO的单元测试时,我最终认为抽象是错误的。

答案 5 :(得分:2)

使用 System.IO.Abstractions System.IO.Abstractions.TestingHelpers 像这样:

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;
   }
}

在测试类中,您使用MockFileSystem()模拟文件,并实例化ManageFile,如:

var mockFileSysteme = new MockFileSystem();
var mockFileData = new MockFileData("File content");
mockFileSysteme.AddFile(mockFilePath, mockFileData );
var manageFile = new ManageFile(mockFileSysteme);

答案 6 :(得分:1)

我不确定如何模拟文件系统。您可以做的是编写一个测试夹具设置,创建一个具有必要的测试结构的文件夹等。在测试运行后,拆卸方法会将其清理干净。

编辑补充:在考虑这个问题时,我不认为你想要模拟文件系统来测试这种类型的方法。如果你模拟文件系统,如果某个文件存在则返回true,并在测试该文件是否存在的方法中使用它,那么你就不会测试任何东西了。模拟文件系统有用的地方是,如果你想测试一个依赖于文件系统的方法,但文件系统活动不是被测试方法的组成部分。

答案 7 :(得分:1)

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

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

作为模拟的替代方法,请考虑在测试设置中创建所需的文件夹和文件,并在拆解方法中删除它们。

答案 8 :(得分:1)

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

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

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

我将承认在mscorlib文件系统对象周围使用包装器是一件痛苦的事情,但在这些特定情况下,随着测试变得更加容易和可靠,它值得额外工作。

答案 9 :(得分:1)

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

答案 10 :(得分:1)

您可以使用Microsoft Fakes执行此操作,而无需更改代码库,例如因为它已被冻结。

System.dll的第一个generate a fake assembly - 或任何其他包,然后模拟预期返回,如:

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
}

答案 11 :(得分:0)

常见的解决方案是使用一些抽象的文件系统API(如Apache Commons VFS for Java):所有应用程序逻辑都使用API​​,单元测试能够使用存根实现模拟真实的文件系统(内存模拟或类似的东西)。

对于C#,存在类似的API:NI.Vfs,它与Apache VFS V1非常相似。它包含本地文件系统和内存文件系统的默认实现(最后一个可用于框中的单元测试)。

答案 12 :(得分:-1)

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

答案 13 :(得分:-2)

我会选择Jamie Ide的回复。不要试图嘲笑你没写的东西。会有各种你不了解的依赖关系 - 密封课程,非虚拟方法等。

另一种方法是使用可模拟的东西包装appopiate方法。例如创建一个名为FileWrapper的类,它允许访问File方法,但是你可以模拟出来。