在测试中易读性比其他要素更为重要。如果一个测试失败了,你希望问题显而易见。开发者不应该需要浏览许多经过大量分解的测试代码才能确定到底哪里出了错。你不希望自己的测试代码变得太过复杂以至于需要编写单元测试的测试。
然而,消除重复通常是一件好事,只要它不会掩盖任何东西,并且消除测试中的重复可能会导致更好的API。只需确保不超过收益递减点即可。
重复的代码在单元测试代码中与其他代码一样容易引起问题。如果测试中有重复的代码,这会使得重构实现代码变得更加困难,因为需要更新的测试数量是不成比例的。测试应该帮助您自信地进行重构,而不是成为阻碍您对被测试代码进行工作的大负担。
如果复制出现在 fixture 设置中,请考虑更多地使用setUp
方法或提供更多(或更灵活的)创建方法。
如果复制出现在操作 SUT 的代码中,请问一下自己,为什么多个所谓的“单元”测试要执行完全相同的功能。
如果复制出现在断言中,那么也许您需要一些自定义断言。例如,如果多个测试具有类似于以下字符串的断言:
assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())
也许你需要一个单独的assertPersonEqual
方法,这样你就可以写assertPersonEqual(Person('Joe', 'Bloggs', 23), person)
。(或者也许你只需要在Person
上重载等号运算符。)
正如你所提到的,测试代码的可读性非常重要。特别是,测试的意图必须清晰明了。我发现,如果许多测试看起来大致相同(例如,四分之三的行相同或几乎相同),那么在不仔细阅读和比较它们的情况下很难发现和识别重要的差异。因此,我发现重构以消除重复有助于可读性,因为每个测试方法的每一行都与测试的目的直接相关。这对读者比直接相关的行和只是样板文件的行的随机组合更有帮助。
尽管如此,有时测试正在测试类似但仍然显着不同的复杂情况,很难找到减少重复的好方法。使用常识:如果您觉得测试可读并清楚地表达其意图,并且您对需要更新的测试数量超过理论上最小数量的情况感到满意,则接受不完美并转向更有成效的事情。当灵感来临时,您总是可以回来重构测试!
实现代码和测试代码是不同的东西,因此它们需要应用不同的规则。
在实现代码中,重复的代码或结构总是一个坏味道。当你在实现中开始出现样板代码时,你需要重新审视你的抽象。
另一方面,测试代码必须保持一定程度的重复。测试代码中的重复达到了两个目标:
我倾向于忽略测试代码中的琐碎重复,只要每个测试方法保持在大约20行以下。我喜欢测试方法中明显的设置-运行-验证节奏。
当测试中的重复出现在“验证”部分时,通常有益于定义自定义的断言方法。当然,这些方法仍然必须测试一个清晰确定的关系,这可以在方法名中表现出来:assertPegFitsInHole
-> 好,assertPegIsGood
-> 不好。
当测试方法变得冗长和重复时,我有时会发现定义一些填空测试模板是有用的,这些模板需要几个参数。然后,实际的测试方法将被缩减为使用适当参数调用模板方法。
对于编程和测试中的很多事情,没有明确的答案。你需要培养一种口味,最好的方法就是犯错误。
您可以使用几种不同类型的测试工具方法来减少重复。
在测试代码中,我对重复比在生产代码中更加容忍,但有时我会感到沮丧。当您更改类的设计并且必须返回并调整所有执行相同设置步骤的10个不同测试方法时,这很令人沮丧。
我同意。权衡存在,但在不同的地方有所不同。
我更倾向于重构重复的设置状态的代码,但不太可能重构实际测试代码部分。话虽如此,如果测试代码部分始终需要多行代码,则我可能认为这是一个问题,并重构受测试的实际代码。这将提高代码和测试的可读性和可维护性。
Jay Fields创造了“DSL应该是DAMP而不是DRY”的短语,其中DAMP代表描述性和有意义的短语。我认为同样适用于测试。显然,过多的重复很糟糕。但是不惜一切代价消除重复更糟糕。测试应该作为意图揭示的规范。例如,如果您从几个不同的角度指定相同的功能,则可以预期出现某种程度的重复。
我认为测试代码需要与生产代码一样的工程水平。虽然可读性是重要的,但根据我的经验,我发现精心因式分解的测试更容易阅读和理解。如果有五个测试看起来完全一样,除了一个变量和最后的断言不同,很难找到这个唯一的不同之处。同样,如果 only 变量和断言可见,那么很容易立即了解测试正在做什么。
在测试时寻找正确的抽象层次可能很困难,但我认为值得去做。
我喜欢 RSpec,因为它具有以下两个帮助功能:
共享的测试用例组,用于测试常见的行为。
您可以定义一组测试,然后在实际测试中“包含”该组。
嵌套上下文。
您可以为特定子集的测试实质上设置“安装”和“拆卸”方法,而不仅仅是类中的每个测试。
.NET/Java/其他测试框架越早采用这些方法,越好(或者您可以使用 IronRuby 或 JRuby 编写测试,我个人认为这是更好的选择)。
我认为代码的重复度和可读性没有直接关系。我认为测试代码应该和其他代码一样优秀。当代码重复得好时,非重复代码比重复代码更易读。