如何在.NET中使用真实文件进行集成testing?
我有一些类实现了一些与文件系统和文件有关的逻辑。 例如,我正在执行以下任务作为此逻辑的一部分:
- 检查某个文件夹是否具有特定的结构(例如,它包含具有特定名称的子文件夹等)
- 从这些文件夹加载一些文件并检查它们的结构(例如,这些是一些configuration文件,位于特定文件夹内的某个地方)
- 从configuration文件加载额外的文件进行testing/validation(例如,这个configuration文件包含有关其他文件在同一文件夹中的信息,应该有其他内部结构等)
现在,所有这些逻辑都有一些工作stream程,并且抛出exception(如果某些内容不正确)(例如,在特定文件夹位置找不到configuration文件)。 此外,这个逻辑中还包含了Managed Extensibility Framework(MEF) ,因为我正在检查的这些文件中有一些是我手动加载到MEF聚合等的pipe理DLL。
现在我想以某种方式testing所有这些。 我正在考虑在硬盘上创build几个物理testing文件夹,涵盖各种testing用例,然后运行我的代码。 我可以创build例如:
- 具有正确结构的文件夹和所有有效的文件
- 文件夹结构正确但configuration文件无效
- 正确的结构,但缺lessconfiguration文件等文件夹…
这是正确的方法吗? 我不确定在这种情况下究竟如何运行我的代码…我当然不想运行整个应用程序,并指出它检查这些模拟文件夹。 我应该使用一些unit testing框架来编写一种“unit testing”,它执行我的代码对这些文件系统对象?
一般来说,这是否是这种testing场景的正确方法? 还有其他更好的方法吗?
首先 ,我认为,最好是编写unit testing来testing你的逻辑而不接触任何外部资源 。 在这里你有两个select:
- 您需要使用抽象层将您的逻辑与外部依赖关系(如文件系统)隔离。 在unit testing中,你可以很容易地用stub或mock(在NSubstitute,FakeItEasy或Moq等受限隔离框架的帮助下)抽象。 我更喜欢这个选项,因为在这种情况下,testing会把你推向更好的devise。
- 如果你必须处理遗留代码(只在这种情况下),你可以使用一个无约束的隔离框架(例如TypeMock Isolator,JustMock或者Microsoft Fakes),它可以存储/模拟几乎所有东西(例如,密封和静态类,非虚拟方法)。 但是他们花钱。 唯一的“免费”选项是Microsoft Fakes,除非您是Visual Studio 2012/2013 Premium / Ultimate的快乐拥有者。
在unit testing中,您不需要testing外部库(如MEF)的逻辑。
其次 ,如果你想编写集成testing ,那么你需要编写“开心path”testing(当一切正常)和一些testing,在边界情况下testing你的逻辑(文件或目录未find)。 不像@Sergey Berezovskiy,我build议为每个testing用例创build单独的文件夹 。 主要优点是:
- 你可以给你的文件夹有意义的名字,更清楚地expression你的意图;
- 你不需要编写复杂的(即脆弱的)设置/拆卸逻辑。
- 即使你以后决定使用另一个文件夹结构,那么你也可以更容易地改变它,因为你已经有了工作代码和testing(在testing工具下重构要容易得多)。
对于unit testing和集成testing, 您可以使用普通的unit testing框架 (如NUnit或xUnit.NET)。 有了这个框架,在构build服务器上的持续集成场景中启动testing非常简单。
如果您决定编写两种testing,则需要将unit testing与集成testing分开(您可以为每种testing创build单独的项目)。 原因:
- unit testing是开发者的安全网。 他们必须在上次代码更改(错误修复,新function)后提供有关系统单元预期行为的快速反馈。 如果它们频繁运行,那么开发人员可以快速轻松地识别代码段,从而破坏系统。 没有人想要运行缓慢的unit testing。
- 集成testing通常比unit testing慢。 但他们有不同的目的。 他们检查单位如预期的那样依靠真正的依赖。
您应该通过抽象调用接口后面的文件系统来testing尽可能多的unit testing逻辑。 使用dependency injection和一个像FakeItEasy这样的testing框架将允许你testing你的接口是否被实际使用/调用来操作文件和文件夹。
然而,在某些时候,你将不得不testing在文件系统上工作的实现,这就是你需要集成testing的地方。
你需要testing的东西似乎是相对孤立的,因为你想testing的是你自己的文件和目录,在你自己的文件系统上。 如果你想testing一个数据库,或者一些其他外部系统与多个用户等,事情可能会更复杂。
我不认为你会发现如何最好地进行这种types的集成testing的任何“官方规则”,但我相信你是在正确的轨道上。 你应该努力的一些想法:
- 清晰的标准:明确每个testing的规则和目的。
- 自动化:能够快速重新运行testing,而无需太多手动调整。
- 可重复性:您可以“重置”的testing情况,因此您可以快速重新运行testing,只需轻微的变化。
创build一个可重复的testing场景
在你的情况下,我会设置两个主文件夹:一个是所有事情都应该是正确的(即正常工作),另一个是所有的规则都被打破了。
我将创build这些文件夹及其中的任何文件,然后压缩每个文件夹,并在testing类中写入逻辑,以解压每个文件夹。
这些不是真正的考验; 将它们视为设置testing场景的“脚本”,即使您的主要集成testing在testing过程中发生变化或混乱,您也可以轻松快速地删除和重新创build文件夹和文件。 把它们放在testing类中的原因,就是简单地使用testing过程中的相同界面来运行。
testing
创build两组testing类,每种情况一套(正确设置文件夹与损坏规则的文件夹)。 将这些testing放在一个文件夹层次结构中,这对你来说是有意义的(取决于你的情况的复杂性)。
目前还不清楚你是如何熟悉单元/集成testing。 无论如何,我会推荐NUnit 。 我喜欢在Should
使用扩展。 你可以从Nuget获得这两个:
install-package Nunit install-package Should
应用程序包将允许您以如下方式编写testing代码:
someCalculatedIntValue.ShouldEqual(3); someFoundBoolValue.ShouldBeTrue();
请注意,有几个testing运行者可用来运行你的testing。 我个人对内置到Resharper的跑步者只有真正的体验,但是我对它很满意,而且我推荐它没有问题。
下面是一个简单的testing类与两个testing的例子。 请注意,首先,我们使用来自Should的扩展方法检查期望值,而在第二个中我们没有明确地testing任何东西。 这是因为它是用[ExpectedException]标记的,这意味着如果运行testing时不抛出指定types的exception,它将会失败。 您可以使用它来validation是否有一个规则被破坏时会引发适当的exception。
[TestFixture] public class When_calculating_sums { private MyCalculator _calc; private int _result; [SetUp] // Runs before each test public void SetUp() { // Create an instance of the class to test: _calc = new MyCalculator(); // Logic to test the result of: _result = _calc.Add(1, 1); } [Test] // First test public void Should_return_correct_sum() { _result.ShouldEqual(2); } [Test] // Second test [ExpectedException(typeof (DivideByZeroException))] public void Should_throw_exception_for_invalid_values() { // Divide by 0 should throw a DivideByZeroException: var otherResult = _calc.Divide(5, 0); } [TearDown] // Runs after each test (seldom needed in practice) public void TearDown() { _calc.Dispose(); } }
有了这一切,您应该能够创build并重新创buildtesting场景,并以简单,可重复的方式对testing场景进行testing。
编辑:正如在评论中指出的, Assert.Throws()是确保exception被抛出的另一种select 。 就个人而言,我喜欢标签variables,并且通过参数 ,你也可以在那里检查错误信息。 另一个例子(假设一个自定义的错误消息是从你的计算器抛出):
[ExpectedException(typeof(DivideByZeroException), ExpectedMessage="Attempted to divide by zero" )] public void When_attempting_something_silly(){ ... }
我会去单个testing文件夹。 对于各种testing用例,您可以将不同的有效/无效文件作为上下文设置的一部分放入该文件夹中。 在testing拆解只是从文件夹中删除这些文件。
例如Specflow :
Given configuration file not exist When something Then foo Given configuration file exists And some dll not exists When something Then bar
将每个上下文设置步骤定义为复制/不将适当的文件复制到您的文件夹。 您还可以使用表来定义哪个文件应该被复制到文件夹中:
Given some scenario | FileName | | a.config | | b.invalid.config | When something Then foobar
我不知道你的程序的架构给出一个好的build议,但我会尝试
- 我相信你不需要testing真正的文件结构 。 文件访问服务由系统/框架定义,不需要testing。 你需要在相关的testing中嘲笑这个服务。
- 你也不需要testingMEF。 它已经被testing了。
- 使用SOLID原则进行unit testing。 尤其是看单一职责原则,这将允许你创buildunit testing,这将不会相互关联。 只是不要忘记嘲笑,以避免依赖。
- 要进行集成testing,您可以创build一组帮助程序类,它们将模拟您要testing的文件结构scheme 。 这样可以让你不用附加到你要运行这个testing的机器上。 这样的方法可能比创build真正的文件结构更复杂,但我喜欢它。
我将构build框架逻辑并testing并发问题和文件系统exception,以确保定义良好的testing环境。
尝试列出问题域的所有边界。 如果太多,那么考虑一下你的问题是否有太多的可能性,需要分解。 什么是使你的系统通过所有testing所必需的充分条件? 然后看看每个条件,并把它当作个人攻击点。 并列出所有你能想到的方法。 试着向自己certificate你已经find了他们。 然后为每个写一个testing。
我将首先对环境进行上述过程,首先build立和testing一个令人满意的标准,然后在工作stream程中提供更详细的逻辑。 如果在testing过程中发生环境和详细逻辑之间的依赖关系,则可能需要进行一些迭代。