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

Posted

技术标签:

【中文标题】如何在 .NET 中使用真实文件进行集成测试?【英文标题】:How to do integration testing in .NET with real files? 【发布时间】:2013-12-14 10:43:10 【问题描述】:

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

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

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

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

结构正确且所有文件均有效的文件夹 结构正确但配置文件无效的文件夹 结构正确但缺少配置文件的文件夹 等等……

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

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

【问题讨论】:

【参考方案1】:

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

    您需要使用抽象层将您的逻辑与文件系统等外部依赖项隔离开来。您可以在单元测试中轻松地对这些抽象进行存根或模拟(手动或借助 NSubstitute、FakeItEasy 或 Moq 等受限隔离框架)。我更喜欢这个选项,因为在这种情况下,测试会促使您做出更好的设计。 如果您必须处理遗留代码(仅在这种情况下),您可以使用可以存根/模拟几乎所有内容(例如,密封和静态类,非虚拟方法)。但他们要花钱。唯一的“免费”选项是 Microsoft Fakes,除非您是 Visual Studio 2012/2013 Premium/Ultimate 的快乐所有者。

在单元测试中你不需要测试MEF等外部库的逻辑。

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

    你可以给你的文件夹起有意义的名字,更清楚地表达你的 意图; 您无需编写复杂(即脆弱)的设置/拆卸逻辑。 即使您稍后决定使用另一个文件夹结构,您也可以更轻松地更改它,因为您已经有了工作代码和测试(在测试工具下重构要容易得多)。

对于单元测试和集成测试,您可以使用普通的单元测试框架(如 NUnit 或 xUnit.NET)。有了这个框架,在构建服务器上的持续集成场景中启动测试非常容易。

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

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

【讨论】:

如何编写单元测试来测试从文件中传入和传出的数据? 像任何其他测试一样 :) 你能说得更具体点吗? 我使用正在测试的软件备份/恢复工具增量备份一个大(几 TB)文件。我将文件从增量恢复到磁盘上。如何单元测试校验和是否相同而不打磁盘? 我发现这个问题的原因是因为我正在寻找一种方法来进行集成测试,而不必在进行过程中拼凑我自己的框架。我个人发现在这种情况下无处不在的“只是伪造一切,没问题”的答案是没有帮助的。我不能轻易伪造网络 IO,或磁盘 IO,或多进程场景,或硬件断开/连接。这些是软件必须处理的有效问题,在某些时候你需要测试它们,而不是用内存中的假货代替它,因此什么也不测试。 +Asad Saeeduddin 考虑到单元和集成测试解决了完全不同的问题,正如您所提到的,答案确实非常无用。一个不能直接替代另一个。【参考方案2】:

您应该使用单元测试来测试尽可能多的逻辑,方法是抽象对接口后面的文件系统的调用。使用依赖注入和诸如FakeItEasy 之类的测试框架将允许您测试您的接口是否实际被使用/调用以对文件和文件夹进行操作。

然而,在某些时候,您还必须测试在文件系统上运行的实现,这就是您需要进行集成测试的地方。

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

我认为您不会找到任何关于如何最好地进行此类集成测试的“官方规则”,但我相信您走在正确的轨道上。您应该努力实现的一些想法:

明确的标准:让每项测试的规则和目的绝对清晰。 自动化:无需过多手动调整即可快速重新运行测试的能力。 可重复性:您可以“重置”的测试情况,因此您可以快速重新运行测试,只有轻微的变化。

创建可重复的测试场景

在您的情况下,我会设置两个主要文件夹:一个是所有内容都按预期进行的文件夹(即正常工作),另一个是所有规则都被破坏的文件夹。

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

这些并不是真正的测试;将它们视为设置测试场景的“脚本”,使您能够轻松快速地删除和重新创建文件夹和文件,即使您的主要集成测试在测试期间应该更改或弄乱它们。将它们放在测试类中的原因只是为了让您在测试期间使用相同的界面轻松运行。

测试

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

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

install-package Nunit
install-package Should

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

someCalculatedIntValue.ShouldEqual(3); 
someFoundBoolValue.ShouldBeTrue();

请注意,有几个可用的测试运行器可以用来运行您的测试。我个人只对 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(); 
    

有了所有这些,您应该能够创建和重新创建测试场景,并以一种简单且可重复的方式对其进行测试。


编辑: 正如评论中所指出的,Assert.Throws() is another option 用于确保根据需要抛出异常。就个人而言,我喜欢标签变体,with parameters,你也可以在那里检查错误消息之类的东西。另一个示例(假设您的计算器正在抛出自定义错误消息):

[ExpectedException(typeof(DivideByZeroException), 
   ExpectedMessage="Attempted to divide by zero" )]
public void When_attempting_something_silly()  
    ...

【讨论】:

与其使用 [ExpectedException],不如使用 Assert.Throws 这是一种可能性——但你为什么认为它比标签更好?有什么具体原因,还是只是口味问题? 1) [ExpectedException] 可以在测试方法的任何行抛出,不仅在“Act”阶段。假阳性结果的可能性略大。 2) Assert.Throws 返回 TException 类型的异常。您可以针对异常的其他成员进行断言。例如,我总是检查 ArgumentException 的 ParamName。 3) ExpectedMessage 的断言也很脆弱。消息显然可以更改。更强大的解决方案是检查异常消息中是否包含重要信息。您可以将 StringAssert.Contains 与 Assert.Throws. 结合使用 谢谢,很好的答案。如果您保持测试方法简洁明了,我认为(1)应该不是一个大问题,但我认为这可能与集成测试相关,它可能(?)比典型的单元测试更复杂。 (2) 如果您需要它是一个好点,但关于 (3),我不清楚如何/何时可以更改消息。在哪里以及为什么会改变?在测试本身?运行后? 1) 误报很难被发现,因为没有人检查通过的测试。 3)您可以考虑在将来改进您的异常消息。您的消息可能很长(例如,github.com/nsubstitute/NSubstitute/blob/master/Source/…),但很有表现力。我不认为你想在测试中复制它(记住,DRY?)。例如,由于这些原因,xUnit.net 没有此属性。【参考方案3】:

我会选择单个测试文件夹。对于各种测试用例,您可以将不同的有效/无效文件作为上下文设置的一部分放入该文件夹中。在测试拆解中,只需从文件夹中删除这些文件。

例如Specflow:

Given configuration file not exist
When something
Then foo

Given configuration file exists
And some dll not exists
When something
Then bar

将每个上下文设置步骤定义为将适当的文件复制/不复制到您的文件夹。您也可以使用table 来定义应将哪个文件复制到文件夹:

Given some scenario
| FileName         |
| a.config         |
| b.invalid.config |
When something
Then foobar

【讨论】:

【参考方案4】:

我不知道你的程序架构可以给出一个好的建议,但我会尝试

    我相信您不需要测试真实的文件结构。文件访问服务由系统/框架定义,无需测试。您需要在相关测试中模拟此服务。 您也不需要测试 MEF。它已经过测试。 使用 SOLID principles 进行单元测试。特别是看看 Single Responsibility Principle 这将允许您创建单元测试,这些单元测试不会相互关联。只是不要忘记模拟以避免依赖。 要进行集成测试,您可以创建一组帮助程序类,它们将模拟您要测试的文件结构场景。这将允许您保持不连接到您将在其上运行此测试的机器。这种方法可能比创建真正的文件结构更复杂,但我喜欢它。

【讨论】:

【参考方案5】:

我将构建框架逻辑并测试并发问题和文件系统异常,以确保定义良好的测试环境。

尝试列出问题域的所有边界。如果有太多,则考虑您的问题定义过于广泛并且需要分解的可能性。使您的系统通过所有测试所需的全套必要和充分条件是什么?然后查看每个条件并将其视为一个单独的攻击点。并列出你能想到的所有违反它的方法。试着向自己证明你已经找到了它们。然后为每个编写一个测试。

我将首先针对环境进行上述过程,首先构建并测试它以达到令人满意的标准,然后再针对工作流程中的更详细的逻辑进行测试。如果您在测试期间遇到环境和详细逻辑之间的依赖关系,则可能需要进行一些迭代。

【讨论】:

以上是关于如何在 .NET 中使用真实文件进行集成测试?的主要内容,如果未能解决你的问题,请参考以下文章

Authorize.net 支付集成[关闭]

如何在 .net framework 4.7 上使用 Mediatr 进行集成测试?

使用 ASP.NET Core 和 Entity Framework Core 进行集成测试 - 如何在每次测试时恢复数据库中的测试数据?

集成测试框架

使用 Visual Studio for Mac 使用 SpecFlow 进行 .NET Core 集成测试

接口测试持续集成是如何进行的呢?