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

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

113852 次浏览

我不确定你会如何模拟文件系统。您可以做的是编写一个测试装置设置,用于创建具有测试所需结构的文件夹等。在测试运行之后,一个拆卸方法可以清理它。

编辑添加: 在进一步考虑这个问题时,我不认为您想要模拟文件系统来测试这种类型的方法。如果您模拟文件系统在某个文件存在时返回 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 实际上并不基于可模拟的接口或可扩展类。

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

作为嘲笑的替代方法,可以考虑只创建作为测试设置的一部分所需的文件夹和文件,然后在 Teardown 方法中删除它们。

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

在这个答案最初被接受时,这个一揽子方案并不存在:

你可以通过创建一个界面来实现:

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

并创建一个“真正的”实现,其中使用 然后可以使用 模拟框架; 我建议使用 莫克

编辑: 有人这样做,并友好地把它在线 给你

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

另一种方法可能是使用 TypeMock,这允许您 拦截对类的调用并将其停止 钱,并将需要安装在你的整个团队的个人电脑和 你的构建服务器才能运行,而且,它显然不会工作 文件,因为它是 不能阻止麦斯科利布

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

我赞成 Jamie Ide 的回答。不要试图模仿你没有写的东西。将会有各种各样你不知道的依赖关系——密封类,非虚方法等等。

另一种方法是用一些可笑的东西来包装阿片类方法。例如,创建一个名为 FileWrapper 的类,该类允许访问 File 方法,但是可以模拟。

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

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

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

我承认使用 mscolib 文件系统对象的包装器是一件痛苦的事情,但是在这些特定的场景中,随着测试变得更加简单和可靠,额外的工作是值得的。

我们目前使用一个专有的数据引擎,它的 API 没有公开为接口,所以我们很难对数据访问代码进行单元测试。然后我也采用了马特和约瑟夫的方法。

创建一个接口并对其进行模拟以进行测试是最简洁的方法。然而,作为一个替代方案,你可以看看 微软鼹鼠框架。

我发现了以下解决方案:

  • 编写集成测试,而不是单元测试。为了实现这一点,您需要一种简单的方法来创建一个文件夹,在这个文件夹中您可以转储内容,而不用担心其他测试的干扰。我有一个简单的 测试文件夹类,它可以为每个测试方法文件夹创建一个唯一的文件夹来使用。
  • 编写一个可仿制的 System.IO.File。那就是创建一个 文件。我发现使用它通常会以简单地证明您可以编写嘲讽语句的测试结束,但是在 IO 使用量很小的时候使用它。
  • 检查抽象层,并从类中提取文件 IO。为此创建一个接口。其余部分使用集成测试(但这将非常小)。这与上面的不同之处在于不做文件。读你写的意图,说 ioThingie.loadSettings ()
  • System.IO.Abstrations 我还没有使用过这个,但是它是我最喜欢玩的一个。

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

Install-Package System.IO.Abstractions

这个 想象出来的库现在已经存在,有一个针对 系统,IO. 抽象的 NuGet 包,它抽象出 System.IO 名称空间。

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

我的建议是使用 < a href = “ http://systemwrapper.codebx.com/”rel = “ norefrer”> http://systemwrapper.codeplex.com/ 因为它为 System 命名空间中最常用的类型提供包装器

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

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

您可以使用 微软赝品来做到这一点,而不需要更改代码库,例如,因为它已经被冻结了。

首先是 System.dll 的 生成一个假的装配-或任何其他包,然后模拟预期的返回值,如下所示:

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
}

像这样使用 系统,IO. 抽象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;
}
}

在 Test Class 中,使用 MockFileSystem ()模拟文件,并实例化 ManageFile,如下所示:

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