Answers:
和其他代码一样,重复的代码在单元测试代码中也是一种气味。如果测试中有重复的代码,则由于要更新的测试数量不成比例,因此很难重构实现代码。测试应该帮助您信心十足地进行重构,而不是成为阻碍您在被测试代码上工作的巨大负担。
如果复制是在夹具设置中,请考虑更多地使用该setUp
方法或提供更多(或更灵活)的创建方法。
如果重复是在操纵SUT的代码中,那么请问自己为什么多个所谓的“单元”测试行使着完全相同的功能。
如果重复出现在断言中,那么您可能需要一些Custom Assertions。例如,如果多个测试具有类似以下的断言字符串:
assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())
然后,也许您需要一个assertPersonEqual
方法,以便您可以编写assertPersonEqual(Person('Joe', 'Bloggs', 23), person)
。(或者也许您只需要在上重载相等运算符Person
。)
正如您提到的,测试代码的可读性很重要。尤其重要的是,明确测试意图。我发现,如果许多测试看起来大致相同(例如,四分之三的线相同或几乎相同),那么在不仔细阅读和比较它们的情况下很难发现并识别出明显的差异。因此,我发现重构消除重复有助于提高可读性,因为每种测试方法的每一行都与测试目的直接相关。与直接相关的行和只是样板的行的随机组合相比,这对读者更有帮助。
就是说,有时测试会遇到类似但仍然明显不同的复杂情况,因此很难找到减少重复的好方法。使用常识:如果您认为测试是可读的并且明确了它们的意图,并且在重构由测试调用的代码时可能会需要更新理论上最少的测试数量,那么您会感到满意,然后接受缺陷并继续前进从事更具生产力的工作。当灵感来袭时,您总是可以稍后返回并重构测试!
可读性对于测试更为重要。如果测试失败,则您希望问题很明显。开发人员不必遍历大量严格的测试代码即可确定失败的确切原因。您不希望您的测试代码变得如此复杂,以至于需要编写单元测试。
但是,消除重复通常是一件好事,只要它不会掩盖任何东西,并且在测试中消除重复可能会带来更好的API。只要确保您不超过收益递减点即可。
实现代码和测试是不同的动物,并且分解规则对它们的应用也不同。
重复的代码或结构始终是实现代码中的一种气味。当您开始实施样板时,您需要修改抽象。
另一方面,测试代码必须保持一定程度的重复。测试代码中的重复实现两个目标:
我倾向于忽略测试代码中的琐碎重复,只要每种测试方法的长度都小于20行即可。我喜欢设置-运行-验证节奏在测试方法中显而易见的情况。
当复制在测试的“验证”部分中蔓延时,定义自定义断言方法通常是有益的。当然,这些方法仍必须测试可以在方法名称中显示的明确标识的关系:assertPegFitsInHole
->良好,assertPegIsGood
->不良。
当测试方法变得冗长而重复时,我有时发现定义带有一些参数的空白填充测试模板很有用。然后,将实际的测试方法简化为使用适当参数调用模板方法。
至于编程和测试中的许多事情,没有明确的答案。您需要养成品味,而最好的方法就是犯错。