重复代码在单元测试中是否更容易被容忍?

Dar*_*zer 104 unit-testing dry code-duplication

我不久前破坏了几次单元测试,当时我经历并重构它们以使它们更加干燥 - 每次测试的意图都不再清晰.似乎在测试的可读性和可维护性之间存在权衡.如果我在单元测试中留下重复的代码,它们更具可读性,但是如果我更改了SUT,我将不得不追踪并更改重复代码的每个副本.

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

Kri*_*son 170

可读性对于测试更重要.如果测试失败,您希望问题显而易见.开发人员不应该花费大量重要的测试代码来确定失败的确切内容.您不希望您的测试代码变得如此复杂,以至于您需要编写单元测试测试.

但是,消除重复通常是一件好事,只要它不会掩盖任何内容,并且消除测试中的重复可能会导致更好的API.只要确保你没有超过收益递减点.

  • 可读性在测试和非测试代码中同样重要。重构不应该损害可读性——它应该增强可读性。 (3认同)
  • 这里提出的一个很好的理由来自 DRY 和单元测试背后的基本假设:重复自己是不好的,因为它会增加改变行为所需的工作。另一方面,如果您必须经常更改单元测试,那么您的单元测试很可能过于严格 - 它们正在测试_how_而不是_what_。理想的单元测试在布置后不会改变。这意味着将 DRY 应用于单元测试不会带来与应用于应用程序代码时一样多的利润。DRY 是对变化的回应。单元测试不应该。 (2认同)

spi*_*piv 66

重复代码是单元测试代码中的气味,与其他代码一样多.如果您在测试中有重复的代码,则会使重构实现代码变得更加困难,因为您需要更新不成比例的测试.测试应该可以帮助您充满信心地进行重构,而不是成为阻碍您对所测试代码进行工作的巨大负担.

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

如果复制是在操作SUT的代码中,那么问问自己为什么多个所谓的"单元"测试正在执行完全相同的功能.

如果复制在断言中,那么您可能需要一些自定义断言.例如,如果多个测试具有一串断言,例如:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())
Run Code Online (Sandbox Code Playgroud)

那么也许您需要一个assertPersonEqual方法,以便您可以编写assertPersonEqual(Person('Joe', 'Bloggs', 23), person).(或者你可能只需要重载相等运算符Person.)

如您所述,测试代码的可读性非常重要.特别是,测试的目的很明确是很重要的.我发现,如果许多测试看起来大致相同(例如,四分之三的线条相同或几乎相同),则很难发现并识别出显着的差异而不仔细阅读和比较它们.所以我发现重构删除重复有助于提高可读性,因为每个测试方法的每一行都与测试的目的直接相关.这对于读者而言比直接相关的随机组合线和仅仅是样板的线更有帮助.

也就是说,有时候测试会运行类似但仍然存在显着差异的复杂情况,很难找到减少重复的好方法.使用常识:如果您觉得测试是可读的并且明确了他们的意图,并且您对在重构测试调用的代码时需要更新理论上最少数量的测试感到满意,那么接受不完美和移动更有成效的事情.当灵感来袭时,您可以随时回来重构测试!

  • "重复代码是单元测试代码中的气味,与其他代码一样多." 没有."如果你在测试中有重复的代码,那么重构实现代码就会变得更加困难,因为你需要进行不成比例的更新测试." 发生这种情况是因为您正在测试私有API而不是公共API. (25认同)
  • 但是为了防止单元测试中的重复代码,通常需要引入新的逻辑.我不认为单元测试应该包含逻辑,因为那时你需要单元测试的单元测试. (12认同)
  • 奇怪的是,我同意 spiv 和 Kristopher Johnson 的答案。我会这样解释:您总是希望避免在测试中重复实现细节,但绝不会以隐藏上下文为代价。删除重复的糟糕选择是继承链和隐藏上下文的类型组合。消除重复的不错选择是工厂、构建器和对象母体。 (2认同)

dda*_*daa 40

实施代码和测试是不同的动物,因子规则适用于它们.

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

另一方面,测试代码必须保持重复级别.测试代码中的重复实现了两个目标:

  • 保持测试分离.由于合同已经发生变化,过多的测试耦合会使得很难更改需要更新的单个故障测试.
  • 保持测试有意义.当单个测试失败时,必须合理地直接找出它正在测试的内容.

我倾向于忽略测试代码中的琐碎重复,只要每个测试方法保持短于大约20行.我喜欢在测试方法中设置运行验证节奏.

当复制在测试的"验证"部分中逐渐增加时,定义自定义断言方法通常是有益的.当然,这些方法仍然必须测试一个明确标识的关系,这种关系可以在方法名称中明显:assertPegFitsInHole- > good,assertPegIsGood- > bad.

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

至于编程和测试中的很多东西,没有明确的答案.你需要培养品味,最好的方法就是犯错误.


Don*_*kby 8

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

我更容忍测试代码中的重复而不是生产代码中的重复,但我有时会对它感到沮丧.当你改变一个类的设计时,你必须回过头来调整10个不同的测试方法,这些方法都做相同的设置步骤,这是令人沮丧的.


stu*_*ell 7

我同意.权衡存在但在不同的地方有所不同.

我更有可能重构重复代码来设置状态.但不太可能重构实际运行代码的测试部分.也就是说,如果执行代码总是需要几行代码,那么我可能会认为这是一种气味并重构了测试中的实际代码.这将提高代码和测试的可读性和可维护性.


Jör*_*tag 5

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