2009-07-06 33 views
108

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

+1

这看起来像其他几个人的副本,其中包括:http://stackoverflow.com/questions/ 664277 /所需文件系统的接口,和实现的在网。 – 2009-07-06 14:54:46

+0

也许试试看pex(http://research.microsoft.com/en-us/projects/pex/filesystem.pdf) – Tinus 2009-07-06 15:07:16

+2

@Mitch:大多数情况下,将数据放入文件系统就足够了,让单元测试运行他们的课程。但是,我遇到了执行许多IO操作的方法,并且通过使用模拟文件系统大大简化了为这些方法设置测试环境。 – 2009-07-06 23:36:18

回答

107

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

当这个答案最初被接受时,这个包不存在。通过创建一个接口

你可以这样做:

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

,创造它采用 System.IO.File.Exists一个“真实”的实施(原来答案在下方提供了历史背景)等等。然后你可以使用一个 模拟框架来模拟这个接口;我建议Moq

编辑:有人做了这个,好心的在线发布here

我用这个方法来模拟出DateTime.UtcNow在ICLOCK 接口(真的真的有用我们的测试,能够控制 时间的流动!),更传统,一个ISqlDataAccess 接口。

另一种方法可能是使用TypeMock,这允许你拦截对类的调用并将它们存根出来。然而,这确实需要花费 钱,并且需要安装在您的整个团队的PC上,并且需要在您的构建服务器上安装 才能运行,而且它显然不适用于System.IO的 。文件,因为它can't stub mscorlib

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

+1

在我看来,创建一个界面如马特在这里描述的是要走的路。我甚至写了一个工具为你生成这样的接口,当试图模拟静态和/或密封类,或非确定性方法(即时钟和随机数生成器)时这很有用。有关更多信息,请参阅http://jolt.codeplex.com。 – 2009-07-06 23:32:56

1

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

编辑补充:在考虑这一点之后,我不认为你想嘲笑文件系统来测试这种类型的方法。如果您将文件系统模拟为在某个文件存在的情况下返回true,并在您检测该文件是否存在的方法中使用该文件系统,那么您就不会进行任何测试。如果你想测试一个依赖于文件系统的方法,但是文件系统活动并不是被测方法不可或缺的一部分,那么在嘲笑文件系统的时候就会有用。

10

您可能需要构建一个合约,以便从文件系统中定义需要的东西,然后为这些功能编写一个包装。此时你可以模拟或者删除实现。

例子:

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

这将是困难的,因为.NET文件API是不是真正基于接口或可能被嘲笑扩展类嘲笑文件系统中进行测试。

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

作为嘲笑的替代方法,可以考虑在测试设置中创建需要的文件夹和文件,并在拆卸方法中删除它们。

-1

我会和Jamie Ide的回应一起去的。不要试图嘲笑你没有写的东西。将会有你不知道的所有依赖关系 - 密封类,非虚拟方法等。

另一种方法是用适当的东西包装应用程序的方法。例如创建一个名为FileWrapper的类,该类允许访问File方法,但是您可以将其模拟出来。

1

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

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

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

我会承认,使用mscorlib文件系统对象的包装是一件很痛苦的事情,但在这些特定的场景中,值得额外的工作,因为测试变得更加容易和可靠。

-1

我们目前使用专有的数据引擎,其API不作为接口公开,所以我们很难单元测试我们的数据访问代码。然后,我也与马特和约瑟夫的方法一起去了。

1

创建一个接口并嘲笑它进行测试是最干净的方法。然而,作为替代品,你可以看看Microsoft Moles框架。

3

我遇到以下解决方案是:

  • 写集成测试,而不是单元测试。为了这个工作,你需要一个简单的方法来创建一个文件夹,在那里你可以转储的东西,而不用担心其他测试干扰。我有一个简单的TestFolder类,它可以创建一个独特的每个测试方法文件夹来使用。
  • 编写一个可嘲弄的System.IO.File。这是创建IFile.cs。我发现使用它通常会以测试证明您可以编写模拟语句,但在IO使用率很小时使用它。
  • 检查你的抽象层,并从类中提取文件IO。为此创建一个接口。其余的使用集成测试(但这将是非常小的)。这与上面的不同之处在于,不是做文件。读你写意图,说ioThingie.loadSettings()
  • System.IO.Abstractions。我还没有使用过,但这是我最喜欢玩的一个。

我最终使用上面的所有方法,这取决于我正在写什么。但是大多数情况下,当我编写单元测试碰到IO时,我最终认为抽象是错误的。

70

Install-Package System.IO.Abstractions

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

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

0
使用一些抽象文件系统API(像 Apache Commons VFS为Java)

常见的解决方案:所有的应用逻辑使用API​​和单元测试是能够用存根实现(内存模拟或类似的东西)来模拟真正的文件系统。

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

0

你可以使用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 
}