如何在.NET中使用真实文件进行集成测试?

mat*_*i82 58 .net c# integration-testing unit-testing file

我有一些类实现了一些与文件系统和文件相关的逻辑.例如,我正在执行以下任务作为此逻辑的一部分:

  • 检查某个文件夹是否具有某种结构(例如,它包含具有特定名称的子文件夹等)
  • 从这些文件夹中加载一些文件并检查它们的结构(例如,这些是一些配置文件,位于某个文件夹中的某个位置)
  • 从配置文件中加载用于测试/验证的其他文件(例如,此配置文件包含有关同一文件夹中其他文件的信息,应具有其他内部结构等...)

现在所有这些逻辑都有一些工作流程,如果某些东西不正确(例如,在特定文件夹位置找不到配置文件),则抛出异常.此外,还有Managed Extensibility Framework(MEF)涉及此逻辑,因为我检查的其中一些文件是我手动加载到MEF聚合等的托管DLL ...

现在我想以某种方式测试所有这些.我想在HDD上创建几个物理测试文件夹,涵盖各种测试用例,然后针对它们运行我的代码.我可以创建例如:

  • 文件夹结构正确,所有文件都有效
  • 文件夹结构正确但配置文件无效
  • 文件夹结构正确但缺少配置文件等...

这是正确的方法吗?我不确定在这种情况下如何运行我的代码...我当然不想运行整个应用程序并指向它来检查这些模拟的文件夹.我应该使用一些单元测试框架来编写一种"单元测试",它会针对这些文件系统对象执行我的代码吗?

总的来说,对于这种测试场景,这一切都是正确的方法吗?还有其他更好的方法吗?

Vla*_*aev 62

首先,我认为,最好在不触及任何外部资源的情况下编写单元测试来测试逻辑.这里有两个选择:

  1. 您需要使用抽象层将逻辑与外部依赖项(如文件系统)隔离开来.您可以轻松地在单元测试中对这种抽象进行存根或模拟(手动或借助受限制的隔离框架,如NSubstitute,FakeItEasy或Moq).我更喜欢这个选项,因为在这种情况下,测试可以帮助您实现更好的设计.
  2. 如果你必须处理遗留代码(仅在这种情况下),你可以使用一个无约束的隔离框架(如TypeMock Isolator,JustMock或Microsoft Fakes),它可以存根/模拟几乎所有东西(例如,密封和静态)类,非虚方法).但他们花钱.除非您是Visual Studio 2012/2013 Premium/Ultimate的快乐所有者,否则唯一的"免费"选项是Microsoft Fakes.

在单元测试中,您不需要测试外部库(如MEF)的逻辑.

其次,如果你想编写集成测试,那么你需要编写"快乐路径"测试(当一切正常时)和一些在边界情况下测试逻辑的测试(文件或目录未找到).与@Sergey Berezovskiy不同,我建议为每个测试用例创建单独的文件夹.主要优点是:

  1. 你可以给你的文件夹有意义的名字,更清楚地表达你的意图;
  2. 你不需要编写复杂的(即脆弱的)设置/拆卸逻辑.
  3. 即使您稍后决定使用其他文件夹结构,也可以更轻松地更改它,因为您已经拥有可用的代码和测试(在测试工具下重构更容易).

对于单元测试和集成测试,您可以使用普通的单元测试框架(如NUnit或xUnit.NET).使用此框架可以非常轻松地在Build服务器上的持续集成方案中启动测试.

如果您决定编写这两种测试,那么您需要将单元测试与集成测试分开(您可以为每种测试创建单独的项目).原因:

  1. 单元测试是开发人员的安全网.他们必须在最后一次代码更改后提供有关系统单元预期行为的快速反馈(错误修复,新功能).如果它们经常运行,那么开发人员可以快速轻松地识别出破坏系统的代码段.没人想要进行慢速单元测试.
  2. 集成测试通常比单元测试慢.但他们有不同的目的.他们检查单元是否按预期工作,具有真正的依赖性.

  • 我找到这个问题的原因是因为我正在寻找一种方法来进行集成测试,而不必像我一样拼凑我自己的框架.我个人发现无处不在的"假冒一切,没问题"的答案在这种情况下无益.我不能轻易伪造网络IO,磁盘IO,或多进程场景,或硬件断开/连接.这些是软件必须处理的有效问题,并且在某些时候你需要对它们进行测试,而不是用内存伪代替它,因此不进行任何测试. (8认同)
  • + Asad Saeeduddin的答案非常无益,正如你所提到的那样,考虑到单元和集成测试可以解决完全不同的问题.一个不是另一个的直接替代品. (2认同)

Kja*_*tan 8

您应该通过抽象调用接口后面的文件系统来测试单元测试尽可能多的逻辑.使用依赖注入和测试框架(如FakeItEasy)将允许您测试您的接口实际被使用/调用以对文件和文件夹进行操作.

但是,在某些时候,您还必须测试在文件系统上工作的实现,这是您需要集成测试的地方.

你需要测试的东西似乎是相对孤立的,因为你想要测试的是你自己的文件和目录,在你自己的文件系统上.如果您想测试数据库或其他具有多个用户的外部系统等,事情可能会更复杂.

我认为你不会找到任何关于如何最好地进行这种类型的集成测试的"官方规则",但我相信你是在正确的轨道上.你应该努力的一些想法:

  • 明确的标准:使每个测试的规则和目的绝对清晰.
  • 自动化:能够快速重新运行测试而无需过多的手动调整.
  • 可重复性:您可以"重置"的测试情况,因此您可以快速重新运行测试,只需稍微调整一下.

创建可重复的测试场景

在你的情况下,我会设置两个主要文件夹:一个是所有内容都应该是(它正常工作),一个是所有规则都被破坏的文件夹.

我会创建这些文件夹及其中的任何文件,然后压缩每个文件夹,并在测试类中编写逻辑以解压缩每个文件夹.

这些并不是真正的考验; 将它们视为用于设置测试场景的"脚本",使您能够轻松快速地删除和重新创建文件夹和文件,即使您的主要集成测试在测试期间应该更改或弄乱它们.将它们放入测试类中的原因很简单,就是在测试过程中使用相同的界面轻松运行它们.

测试

创建两组测试类,每种情况设置一组(正确设置文件夹与具有损坏规则的文件夹).将这些测试放在对您有意义的文件夹层次结构中(取决于您的情况的复杂程度).

目前尚不清楚您对单元/集成测试的熟悉程度.无论如何,我会推荐NUnit.我也喜欢使用扩展名Should.你可以从Nuget获得这两个:

install-package Nunit
install-package Should
Run Code Online (Sandbox Code Playgroud)

should-package将允许您以如下方式编写测试代码:

someCalculatedIntValue.ShouldEqual(3); 
someFoundBoolValue.ShouldBeTrue();
Run Code Online (Sandbox Code Playgroud)

请注意,有几个测试运行器可用于运行测试.我个人对于Resharper内置的跑步者只有任何实际经验,但我对此非常满意并且推荐它没有任何问题.

下面是一个带有两个测试的简单测试类的示例.请注意,在第一个中,我们使用来自Should的扩展方法检查预期值,而我们没有在第二个中明确测试任何内容.这是因为它用[ExpectedException]标记,这意味着如果在运行测试时没有抛出指定类型的异常,它将失败.您可以使用此方法验证是否在您的某个规则被破坏时抛出了适当的异常.

[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(); 
    }
}
Run Code Online (Sandbox Code Playgroud)

完成所有这些后,您应该能够创建并重新创建测试场景,并以简单且可重复的方式对它们运行测试.


编辑:正如评论中所指出的,Assert.Throws()是另一种确保根据需要抛出异常的选项.就个人而言,我喜欢tag-variant,并且通过参数,您可以检查错误消息等内容.另一个例子(假设您的计算器抛出了自定义错误消息):

[ExpectedException(typeof(DivideByZeroException), 
   ExpectedMessage="Attempted to divide by zero" )]
public void When_attempting_something_silly(){  
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 1)[ExpectedException]可以在任何测试方法中抛出,而不仅仅是在"Act"阶段.假阳性结果的可能性略大.2)Assert.Throws <TException>返回TException类型的异常.您可以断言其他异常成员.例如,我总是检查ArgumentException的ParamName.3)ExpectedMessage的断言也很脆弱.消息显然可以改变.更强大的解决方案是检查异常消息中是否包含重要信息.您可以将StringAssert.Contains与Assert.Throws <>结合使用. (4认同)