您如何在 C# 中模拟文件系统以进行单元测试?
Posted
技术标签:
【中文标题】您如何在 C# 中模拟文件系统以进行单元测试?【英文标题】:How do you mock out the file system in C# for unit testing? 【发布时间】:2010-11-08 09:51:38 【问题描述】:是否有任何库或方法可以在 C# 中模拟文件系统来编写单元测试?在我目前的情况下,我有一些方法可以检查某个文件是否存在并读取创建日期。将来我可能需要更多。
【问题讨论】:
这看起来像是其他几个的副本,包括:***.com/questions/664277/…。 也许可以试试 pex (research.microsoft.com/en-us/projects/pex/filesystem.pdf) @Mitch:大多数时候,将数据放在文件系统中并让单元测试运行就足够了。但是,我遇到过执行许多 IO 操作的方法,通过使用模拟文件系统,为这些方法设置测试环境大大简化了。 我为此写了github.com/guillaume86/VirtualPath(还有更多),它仍然是 WIP,API 肯定会改变,但它已经工作了,并且包含了一些测试。 【参考方案1】:编辑:安装 NuGet 包System.IO.Abstractions
。
最初接受此答案时,此包不存在。原始答案是针对以下历史背景提供的:
你可以通过创建一个界面来做到这一点:
interface IFileSystem bool FileExists(string fileName); DateTime GetCreationDate(string fileName);
并创建一个“真正的”实现,它使用 System.IO.File.Exists() 等。然后您可以使用 模拟框架;我推荐Moq。
编辑:有人这样做了,请在网上发布here。
我已经使用这种方法在 IClock 中模拟了 DateTime.UtcNow 接口(对于我们的测试能够控制真的非常有用 时间的流动!),以及更传统的 ISqlDataAccess 界面。
另一种方法可能是使用TypeMock,这允许您 拦截对类的调用并将它们存根。然而,这确实要花费 钱,并且需要安装在您整个团队的 PC 上,并且 为了运行您的构建服务器,它显然也不适用于 System.IO.File,因为它是can't stub mscorlib。
您也可以接受某些方法不可单元测试 并在单独的慢速集成/系统测试中测试它们 套房。
【讨论】:
在我看来,按照 Matt 在这里的描述创建一个界面是可行的方法。我什至编写了一个为您生成此类接口的工具,这在尝试模拟静态和/或密封类或非确定性方法(即时钟和随机数生成器)时很有用。请参阅jolt.codeplex.com 了解更多信息。 看起来引用文章中的 repo 已被删除/移动,恕不另行通知。但是,这里似乎有一个 nuget 包:nuget.org/packages/mscorlib-mock Typemock 确实对可伪造的类型有限制,但(至少在 2017 年 10 月的当前版本中)您绝对可以伪造 File 静态类。我刚刚自己验证了这一点。 你能总结一些集成测试套件吗?【参考方案2】:这个虚构的库现在存在,有一个用于System.IO.Abstractions 的 NuGet 包,它抽象了 System.IO 命名空间。
还有一组测试助手 System.IO.Abstractions.TestingHelpers - 在撰写本文时 - 仅部分实现,但这是一个非常好的起点。
【讨论】:
我认为围绕这个已经构建的抽象进行标准化是最好的选择。从来没有听说过那个图书馆,所以非常感谢您的提醒。 PM 代表包管理器..打开...工具> NuGet包管理器>包管理器控制台【参考方案3】:您可能必须建立一个合同来定义您需要从文件系统中获得什么,然后围绕这些功能编写一个包装器。到那时,您就可以模拟或存根实现。
例子:
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");
【讨论】:
【参考方案4】:我的建议是使用http://systemwrapper.codeplex.com/ 因为它为系统命名空间中最常用的类型提供了包装器
【讨论】:
我目前正在使用这个库,现在我发现它对 FileStream 等事物的抽象不包括 IDisposable,我正在寻找替代品。如果库不允许我正确处理流,那么我不能推荐(或使用)它来处理这些类型的操作。 SystemWrapper 的 IFileStreamWrap 现在实现了 IDisposable。 systemwrapper 只是 .net 框架,如果与 .netcore 一起使用会导致奇怪的问题【参考方案5】:像这样使用 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】:我遇到了以下解决方案:
编写集成测试,而不是单元测试。为此,您需要一种简单的方法来创建一个文件夹,您可以在其中转储内容,而不必担心其他测试会干扰。我有一个简单的TestFolder 类,它可以为每个测试方法创建一个唯一的文件夹来使用。 编写一个可模拟的 System.IO.File。那就是创建一个IFile.cs。我发现使用它通常会以简单地证明您可以编写模拟语句的测试结束,但在 IO 使用量较小时使用它。 检查抽象层,并从类中提取文件 IO。为此创建一个接口。其余的使用集成测试(但这将非常小)。这与上面的不同之处在于,不是做 file.Read 你写的意图,比如 ioThingie.loadSettings() System.IO.Abstractions。我还没有用过这个,但它是我最喜欢玩的一个。我最终会使用上述所有方法,具体取决于我所写的内容。但大多数时候,当我编写遇到 IO 的单元测试时,我最终会认为抽象是错误的。
【讨论】:
IFile.cs 的链接已损坏。【参考方案7】:您可以使用 Microsoft Fakes 做到这一点,而无需更改您的代码库,例如因为它已经被冻结了。
首先generate a fake assembly 用于 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
【讨论】:
【参考方案8】:在测试中模拟文件系统会很困难,因为 .NET 文件 API 并不是真正基于可以模拟的接口或可扩展类。
但是,如果您有自己的功能层来访问文件系统,则可以在单元测试中模拟它。
作为模拟的替代方法,考虑只创建您需要的文件夹和文件作为测试设置的一部分,然后在您的拆卸方法中删除它们。
【讨论】:
【参考方案9】:我不确定您将如何模拟文件系统。您可以做的是编写一个测试夹具设置,该设置创建一个文件夹等,并具有测试所需的结构。测试运行后,拆卸方法会对其进行清理。
编辑添加:在考虑更多一点时,我认为您不想模拟文件系统来测试这种类型的方法。如果您模拟文件系统以在某个文件存在时返回 true 并在测试检查该文件是否存在的方法中使用它,那么您不会测试任何东西。如果您想测试一个依赖于文件系统但文件系统活动不是被测方法不可或缺的方法,那么模拟文件系统会很有用。
【讨论】:
【参考方案10】:回答您的具体问题:不,没有库可以让您模拟文件 I/O 调用(据我所知)。这意味着“正确”单元测试您的类型将要求您在定义类型时考虑此限制。
关于我如何定义“适当的”单元测试的简要说明。我相信单元测试应该确认您获得了提供已知输入的预期输出(例如异常、方法调用等)。这允许您将单元测试条件设置为一组输入和/或输入状态。我发现做到这一点的最佳方法是使用基于接口的服务和依赖注入,以便类型外部的每个职责都通过构造函数或属性传递的接口提供。
所以,考虑到这一点,回到你的问题。我通过创建IFileSystemService
接口和FileSystemService
实现来模拟文件系统调用,这只是mscorlib 文件系统方法的外观。然后我的代码使用 IFileSystemService
而不是 mscorlib 类型。这允许我在应用程序运行时插入我的标准FileSystemService
,或者在我的单元测试中模拟IFileSystemService
。无论应用程序如何运行,应用程序代码都是相同的,但底层基础架构允许轻松测试该代码。
我承认在 mscorlib 文件系统对象周围使用包装器很痛苦,但在这些特定情况下,额外的工作是值得的,因为测试变得更加容易和更加可靠。
【讨论】:
【参考方案11】:创建一个接口并模拟它以进行测试是最干净的方法。但是,作为替代方案,您可以查看Microsoft Moles 框架。
【讨论】:
【参考方案12】:常见的解决方案是使用一些抽象的文件系统 API(如用于 Java 的 Apache Commons VFS):所有应用程序逻辑都使用 API,并且单元测试能够模拟具有存根实现的真实文件系统(内存中模拟或类似的东西)。
对于 C#,存在类似的 API:NI.Vfs,它与 Apache VFS V1 非常相似。它包含本地文件系统和内存文件系统的默认实现(最后一个可用于单元测试)。
【讨论】:
【参考方案13】:我们目前使用专有的数据引擎,它的 API 没有作为接口公开,因此我们几乎无法对我们的数据访问代码进行单元测试。然后我也采用了马特和约瑟夫的方法。
【讨论】:
【参考方案14】:我会同意 Jamie Ide 的回应。不要试图嘲笑你没有写的东西。会有各种你不知道的依赖关系——密封类、非虚拟方法等。
另一种方法是用可模拟的东西包装 appopiate 方法。例如创建一个名为 FileWrapper 的类,它允许访问 File 方法,但您可以模拟出来。
【讨论】:
以上是关于您如何在 C# 中模拟文件系统以进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章
如何在单元测试中模拟 ServletContext 以访问 WAR 资源?
如何在 Spring Security Bean 中模拟自定义 UserService 详细信息以进行单元测试?