单元测试中重复代码的容忍度更高吗?

时间:2020-03-06 14:41:15  来源:igfitidea点击:

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

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

解决方案

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

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

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

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

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

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

我同意。折衷的存在,但在不同的地方是不同的。

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

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

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

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

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

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

当复制在测试的"验证"部分中蔓延时,定义自定义断言方法通常是有益的。当然,这些方法仍必须测试一个清楚标识的关系,该关系可以在方法名称中清楚地看到:" assertPegFitsInHole"->好," assertPegIsGood"-> bad。

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

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

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

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

"重构它们以使其更干燥-每次测试的意图不再明确"

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

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

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

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

我喜欢rspec是因为:

它有两件事需要帮助-

  • 共享示例组以测试常见行为。我们可以定义一组测试,然后在实际测试中"包含"该测试。
  • 嵌套的上下文。实际上,我们可以为测试的特定子集提供一个"设置"和"拆卸"方法,而不仅仅是类中的每个方法。

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

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

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

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

如果重复出现在断言中,则可能需要一些自定义断言。例如,如果多个测试具有一串断言,例如:

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

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

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

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

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