按需求或方法划分单元测试


16

首先,对标题表示歉意,我想不出最简单的解释方法!

我有一个要为其编写单元测试的方法。我将使其保持相当通用,因为我不想讨论该方法的实现,而只是讨论它的测试。方法是:

public void HandleItem(item a)
{         
     CreateNewItem();
     UpdateStatusOnPreviousItem();
     SetNextRunDate();
}

因此,此类有一个公共方法,然后调用一些私有方法来执行逻辑。

因此,在编写单元测试时,我想检查所有三件事。因为它们都在同一运行中被调用,所以我认为我可以将其作为一项测试:

public void GivenItem_WhenRun_Thenxxxxx
{
     HandleItem(item);
     // Assert item has been created
     // Assert status has been set on the previous item
     // Assert run date has been set
}

但我认为我也可以将其编写为三个单独的测试:

public void GivenItem_WhenRun_ThenItemIsCreated()
{
    HandleItem(item);
}

public void GivenItem_WhenRun_ThenStatusIsUpdatedOnPreviousItem()
{
   HandleItem(item);
}

public void GivenItem_WhenRun_ThenRunDateIsSet()
{
     HandleItem(item);
}

因此,对我来说,这似乎更好,因为它本质上是列出要求,但随后这三个都相关,并且确实需要在测试方法上执行完全相同的工作,因此,将相同的代码运行3次。

是否有建议的方法来处理?

谢谢

Answers:


29

两种方法之间存在细微的差异。在第一种情况下,当第一个Assert失败时,其他两个不再运行。在第二种情况下,即使一个测试失败,所有三个测试也始终运行。根据测试功能的性质,这可能适合您的情况,也可能不太适合您的情况:

  • 如果可以将三个断言彼此独立地运行是有意义的,因为当一个断言失败时,其他两个断言可能仍然不会失败,那么第二种方法的优点是您可以一次运行获得所有三个测试的完整测试结果。如果您有显着的构建时间,这将是有益的,因为它使您有机会在进行下一个构建之前一次修复最多3个错误。

  • 但是,如果第一次测试失败总是意味着其他两个测试也都会失败,那么最好使用第一种方法(因为如果您已经事先知道,运行测试就没有多大意义了,失败)。


2
+1,好点。在我看来,构建时间也可能成为瓶颈。
Kilian Foth

1
@KilianFoth:您使用C ++的频率不够高:(
Matthieu

1
@MatthieuM .:说句公道话,这个问题被标记为“ C#”
Doc Brown

10

答案很简单:它更重要的是你的测试覆盖比所有的功能如何,他们做到这一点。

更长的答案:如果您仍然想在这些大致相同的解决方案中进行选择,则可以使用辅助标准来确定最佳方案。例如,

  • 可读性:如果该方法执行了很多不密切相关的事情,那么组合测试可能很难理解。但是,该方法本身也可能很难理解,因此也许您应该重构该方法而不是测试!)
  • 效率:如果执行该方法花费的时间很长,那么这可能是合并所有三个检查以节省时间的一个较弱的原因
  • 效率2:如果运行框架的设置代码需要花费很长时间,那么这也是避免使用多种测试方法的一个较弱的原因。(但是,如果这确实是一个问题,那么您可能应该修复或更改您的测试设置-如果无法快速运行,则回归测试会失去很多价值。)

2

将一个方法调用与多个断言一起使用。原因如下:

当您测试HandleItem(a)时,您正在测试该方法已使该项进入正确的状态。与其“每个测试一个断言”,不如“每个测试一个逻辑概念”。

问题:如果CreateNewItem失败,但其他两个方法成功,这是否意味着HandleItem成功完成?我猜不是。

使用多个断言(带有适当的消息),您将确切知道失败的原因。您通常会针对多个输入或输入状态多次测试一个方法,而不是避免多个断言。

IMO,这些问题通常是其他问题的征兆。这表明HandleItem实际上不是可以“单元测试”的东西,因为它似乎只是委托给其他方法。当您只是简单地验证HandleItem正确调用其他方法时,它就更像是一个集成测试候选对象(在这种情况下,您仍然有3个断言)。

您可能需要考虑公开其他三种方法并分别对其进行测试。甚至将它们提取到另一个类中。


0

使用第二种方法。使用第一种方法,如果测试失败,您将不会立即知道为什么,因为它可能是失败的三个功能之一。使用第二种方法,您将立即知道问题出在哪里。您可以将重复的代码放入“测试设置”函数中。


-1

恕我直言,您应该分别测试此方法的三个部分,以便更具体地了解问题出在哪里,同时避免重复检查同一部分代码。


-2

我认为没有必要为用例编写单独的测试方法。如果要从所有三个可变条件中获取结果,则可以测试所有三个条件并通过将它们连接到a中string并在完成测试后断言字符串是否仍然为空来打印其失败信息。通过将它们全部保留在同一方法中,您的条件和失败消息将方法的预期后置条件记录在一个地方,而不是将其分为三个方法,以后可能会分开。

这确实意味着您的单个测试将具有更多的代码,但是如果您有如此多的后置条件,那么这可能是一个问题,那么您可能想重构测试方法以测试内部的各个方法HandleItem

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.