单元测试中重复的代码是否更容忍?


113

不久前,当我通过检查并重构它们以使其更加干燥时,我破坏了多个单元测试-每个测试的目的不再明确。似乎在测试的可读性和可维护性之间需要权衡。如果我将重复的代码留在单元测试中,则它们更具可读性,但是如果我更改SUT,则必须跟踪并更改重复的代码的每个副本。

您是否同意这种权衡存在?如果是这样,您是否希望测试具有可读性或可维护性?

Answers:


68

和其他代码一样,重复的代码在单元测试代码中也是一种气味。如果测试中有重复的代码,则由于要更新的​​测试数量不成比例,因此很难重构实现代码。测试应该帮助您信心十足地进行重构,而不是成为阻碍您在被测试代码上工作的巨大负担。

如果复制是在夹具设置中,请考虑更多地使用该setUp方法或提供更多(或更灵活)的创建方法

如果重复是在操纵SUT的代码中,那么请问自己为什么多个所谓的“单元”测试行使着完全相同的功能。

如果重复出现在断言中,那么您可能需要一些Custom Assertions。例如,如果多个测试具有类似以下的断言字符串:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

然后,也许您需要一个assertPersonEqual方法,以便您可以编写assertPersonEqual(Person('Joe', 'Bloggs', 23), person)。(或者也许您只需要在上重载相等运算符Person。)

正如您提到的,测试代码的可读性很重要。尤其重要的是,明确测试意图。我发现,如果许多测试看起来大致相同(例如,四分之三的线相同或几乎相同),那么在不仔细阅读和比较它们的情况下很难发现并识别出明显的差异。因此,我发现重构消除重复有助于提高可读性,因为每种测试方法的每一行都与测试目的直接相关。与直接相关的行和只是样板的行的随机组合相比,这对读者更有帮助。

就是说,有时测试会遇到类似但仍然明显不同的复杂情况,因此很难找到减少重复的好方法。使用常识:如果您认为测试是可读的并且明确了它们的意图,并且在重构由测试调用的代码时可能会需要更新理论上最少的测试数量,那么您会感到满意,然后接受缺陷并继续前进从事更具生产力的工作。当灵感来袭时,您总是可以稍后返回并重构测试!


29
“重复的代码在单元测试代码中就像在其他代码中一样有气味。” 不会。“如果测试中有重复的代码,则由于要更新的​​测试数量不成比例,因此很难重构实现代码。” 发生这种情况是因为您正在测试专用API而不是公用API。

15
但是为了防止单元测试中的代码重复,通常需要引入新的逻辑。我认为单元测试不应包含逻辑,因为那样您就需要单元测试的单元测试。
Petr Peller

@ user11617,请定义“私有API”和“公共API”。以我的理解,公共Api是对外部世界/第三方消费者可见并通过SemVer或类似方式显式版本化的Api,其他任何东西都是私有的。有了这个定义,几乎所有的单元测试都在测试“私有API”,因此对代码复制更加敏感,我认为这是事实。
KolA

@KolA“公开”并不表示第三方消费者-这不是Web API。类的公共API指的是供客户端代码使用的方法(通常不会/不应进行太多更改)-通常是“公共”方法。专用API是指内部使用的逻辑和方法。这些不应从类外部访问。这就是为什么使用访问修饰符或所用语言的约定将逻辑正确封装在类中很重要的原因之一。
弥敦道

@Nathan任何库/ dll / nuget程序包都具有第三方使用者,它不一定是网络api。我所指的是,声明不应该由库使用者直接使用的公共类和成员(或充其量使其内部并使用InternalsVisibleToAttribute注释程序集)只是为了允许单元测试直接到达它们,这是很常见的。它导致大量的测试与实施相结合,并使它们更多的是负担而不是优势
KolA

186

可读性对于测试更为重要。如果测试失败,则您希望问题很明显。开发人员不必遍历大量严格的测试代码即可确定失败的确切原因。您不希望您的测试代码变得如此复杂,以至于需要编写单元测试。

但是,消除重复通常是一件好事,只要它不会掩盖任何东西,并且在测试中消除重复可能会带来更好的API。只要确保您不超过收益递减点即可。


xUnit和其他在断言调用中包含“消息”参数。放置有意义的短语以使开发人员快速找到失败的测试结果的好主意。
2012年

1
@seand您可以尝试解释您的断言正在检查的内容,但是当它失败并且包含有些模糊的代码时,开发人员将需要继续展开它。IMO更重要的是要有代码在此处进行自我描述。
IgorK 2012年

1
@Kristopher ,?为什么发布此为社区Wiki?
Pacerier,2015年

@Pacerier我不知道。关于事物自动成为社区Wiki曾经有复杂的规则。
克里斯托弗·约翰逊

由于报告的可读性比测试更重要,特别是在进行集成或端到端测试时,场景可能足够复杂,可以避免进行少量操作,可以查找失败,但对我而言,报告中的失败应该再次出现解释好这个问题。
Anirudh

47

实现代码和测试是不同的动物,并且分解规则对它们的应用也不同。

重复的代码或结构始终是实现代码中的一种气味。当您开始实施样板时,您需要修改抽象。

另一方面,测试代码必须保持一定程度的重复。测试代码中的重复实现两个目标:

  • 保持测试分离。过多的测试耦合会导致难以更改需要更新的单个失败测试,​​因为合同已更改。
  • 孤立地保持测试有意义。当单个测试失败时,必须相当容易直接找出确切的测试内容。

我倾向于忽略测试代码中的琐碎重复,只要每种测试方法的长度都小于20行即可。我喜欢设置-运行-验证节奏在测试方法中显而易见的情况。

当复制在测试的“验证”部分中蔓延时,定义自定义断言方法通常是有益的。当然,这些方法仍必须测试可以在方法名称中显示的明确标识的关系:assertPegFitsInHole->良好,assertPegIsGood->不良。

当测试方法变得冗长而重复时,我有时发现定义带有一些参数的空白填充测试模板很有用。然后,将实际的测试方法简化为使用适当参数调用模板方法。

至于编程和测试中的许多事情,没有明确的答案。您需要养成品味,而最好的方法就是犯错。


8

我同意。权衡存在,但在不同地方有所不同。

我更有可能重构重复的代码以设置状态。但是不太可能重构实际执行代码的测试部分。就是说,如果执行代码总是要花费几行代码,那么我可能认为这很奇怪,因此可以重构测试中的实际代码。这将提高代码和测试的可读性和可维护性。


我认为这是个好主意。如果您有很多重复项,请查看是否可以重构以创建可以在其中运行许多测试的通用“测试装置”。这将消除重复的设置/拆卸代码。
Outlaw程序员

8

您可以使用几种不同的测试实用程序方法来减少重复。

与生产代码相比,我对测试代码的重复容忍度更高,但有时我对此感到沮丧。当您更改班级的设计时,您必须返回并调整10种不同的测试方法,这些方法都执行相同的设置步骤,这令人沮丧。


6

Jay Fields创造了一个短语“ DSL应该是DAMP,而不是DRY”,其中DAMP表示描述性和有意义的短语。我认为测试也是如此。显然,过多的重复是不好的。但是不惜一切代价删除重复项甚至更糟。测试应作为意图揭示规范。例如,如果您从几个不同的角度指定相同的特征,那么将需要一定程度的重复。


3

我喜欢rspec是因为:

它有两件事需要帮助-

  • 共享示例组以测试常见行为。
    您可以定义一组测试,然后在实际测试中“包含”该测试。

  • 嵌套上下文。
    实际上,您可以为测试的特定子集提供一个“设置”和“拆卸”方法,而不仅仅是类中的每个方法。

.NET / Java /其他测试框架越早采用这些方法,效果越好(或者您可以使用IronRuby或JRuby编写测试,我个人认为这是更好的选择)


3

我觉得测试代码需要类似的工程水平,通常可以应用于生产代码。肯定会有支持可读性的论点,我同意这很重要。

但是,根据我的经验,我发现结构合理的测试更易于阅读和理解。如果有5个测试,每个测试看起来相同,除了一个已更改的变量和最后的断言,很难找到那个不同的项目是什么。同样,如果将其分解,以便仅显示正在更改的变量和断言,那么很容易找出立即执行的测试。

在测试时找到正确的抽象级别可能很困难,我觉得这是值得做的。


2

我认为更多重复的代码和可读的代码之间没有关系。我认为您的测试代码应该和其他代码一样好。如果做得好,非重复代码比重复代码更具可读性。


2

理想情况下,单元测试一旦编写就不会有太大变化,因此我倾向于可读性。

单元测试尽可能地离散,也有助于使测试专注于目标测试的特定功能。

话虽如此,我确实倾向于尝试并重复使用我反复使用的某些代码段,例如在一组测试中完全相同的设置代码。


2

“重构它们以使其更干燥-每次测试的目的不再明确”

听起来您在进行重构时遇到了麻烦。我只是在猜测,但是如果最后的结果不够清晰,这是否意味着您还有更多工作要做,以便您可以进行相当清晰的合理测试?

这就是为什么测试是UnitTest的子类的原因-因此您可以设计正确,易于验证和清除的良好测试套件。

在过去,我们拥有使用不同编程语言的测试工具。很难(或不可能)设计令人愉悦且易于使用的测试。

您拥有使用任何语言的全部能力-Python,Java,C#-因此请很好地使用该语言。您可以实现清晰且不太多余的漂亮测试代码。没有权衡。

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.