在单元测试中重复的代码是否更容易被容忍?
不久前,当我经历并重构它们以使它们更加干——每次测试的目的不再明确。 测试的可读性和可维护性之间似乎需要权衡。 如果我在单元测试中保留重复的代码,它们会更具可读性,但是如果我更改 SUT,我必须追踪并更改重复代码的每个副本。
您同意这种权衡的存在吗? 如果是这样,您希望您的测试具有可读性还是可维护性?
I ruined several unit tests some time ago when I went through and refactored them to make them more DRY--the intent of each test was no longer clear. It seems there is a trade-off between tests' readability and maintainability. If I leave duplicated code in unit tests, they're more readable, but then if I change the SUT, I'll have to track down and change each copy of the duplicated code.
Do you agree that this trade-off exists? If so, do you prefer your tests to be readable, or maintainable?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
可读性对于测试来说更重要。 如果测试失败,您希望问题显而易见。 开发人员不必费力地检查大量经过大量考虑的测试代码来确定到底是什么失败了。 您不希望您的测试代码变得如此复杂,以至于需要编写单元测试。
然而,消除重复通常是一件好事,只要它不会掩盖任何东西,并且消除测试中的重复可能会带来更好的 API。 只要确保您不会超过收益递减点即可。
Readability is more important for tests. If a test fails, you want the problem to be obvious. The developer shouldn't have to wade through a lot of heavily factored test code to determine exactly what failed. You don't want your test code to become so complex that you need to write unit-test-tests.
However, eliminating duplication is usually a good thing, as long as it doesn't obscure anything, and eliminating the duplication in your tests may lead to a better API. Just make sure you don't go past the point of diminishing returns.
实现代码和测试是不同的动物,分解规则对它们的应用也不同。
重复的代码或结构始终是实现代码中的一种味道。 当您开始在实现中使用样板时,您需要修改您的抽象。
另一方面,测试代码必须保持一定程度的重复。 测试代码的重复可以实现两个目标:
只要每个测试方法的长度少于 20 行,我倾向于忽略测试代码中的琐碎重复。 我喜欢测试方法中明显的设置-运行-验证节奏。
当测试的“验证”部分出现重复时,定义自定义断言方法通常是有益的。 当然,这些方法仍然必须测试明确标识的关系,该关系可以在方法名称中显而易见:
assertPegFitsInHole
-> 好,assertPegIsGood
-> 坏的。当测试方法变得冗长且重复时,我有时发现定义带有一些参数的填空测试模板很有用。 然后,实际的测试方法被简化为使用适当的参数调用模板方法。
至于编程和测试中的很多东西,并没有明确的答案。 你需要培养品味,而最好的方法就是犯错误。
Implementation code and tests are different animals and factoring rules apply differently to them.
Duplicated code or structure is always a smell in implementation code. When you start having boilerplate in implementation, you need to revise your abstractions.
On the other hand, testing code must maintain a level of duplication. Duplication in test code achieves two goals:
I tend to ignore trivial duplication in test code as long as each test method stays shorter than about 20 lines. I like when the setup-run-verify rhythm is apparent in test methods.
When duplication creeps up in the "verify" part of tests, it is often beneficial to define custom assertion methods. Of course, those methods must still test a clearly identified relation that can be made apparent in the method name:
assertPegFitsInHole
-> good,assertPegIsGood
-> bad.When test methods grow long and repetitive I sometimes find it useful to define fill-in-the-blanks test templates that take a few parameters. Then the actual test methods are reduced to a call to the template method with the appropriate parameters.
As for a lot of things in programming and testing, there is no clear-cut answer. You need to develop a taste, and the best way to do so is to make mistakes.
重复的代码在单元测试代码中就像在其他代码中一样是一种味道。 如果测试中有重复的代码,那么重构实现代码就会变得更加困难,因为需要更新的测试数量不成比例。 测试应该帮助您充满信心地进行重构,而不是成为阻碍您对正在测试的代码进行工作的巨大负担。
如果重复是在夹具设置中,请考虑更多地使用
setUp
方法或提供更多(或更灵活)创建方法。如果重复出现在操作 SUT 的代码中,那么问问自己为什么多个所谓的“单元”测试正在执行完全相同的功能。
如果断言中有重复,那么您可能需要一些自定义断言。 例如,如果多个测试具有一串断言,例如:
那么您可能需要一个
assertPersonEqual
方法,以便可以编写assertPersonEqual(Person('Joe', 'Bloggs', 23 ),人)
。 (或者您可能只需要重载Person
上的相等运算符。)正如您提到的,测试代码的可读性非常重要。 尤其重要的是,测试的意图必须明确。 我发现,如果许多测试看起来大部分相同(例如,四分之三的行相同或几乎相同),则在不仔细阅读和比较它们的情况下很难发现和识别显着差异。 所以我发现重构以消除重复有助于可读性,因为每个测试方法的每一行都与测试的目的直接相关。 这对读者来说比随机组合直接相关的行和只是样板的行更有帮助。
也就是说,有时测试会执行类似但仍然显着不同的复杂情况,并且很难找到减少重复的好方法。 使用常识:如果您认为测试具有可读性并明确了其意图,并且在重构测试调用的代码时可能需要更新超过理论上最少数量的测试,那么您可以接受缺陷并移动去做一些更有成效的事情。 当灵感来袭时,您随时可以回来重构测试!
Duplicated code is a smell in unit test code just as much as in other code. If you have duplicated code in tests, it makes it harder to refactor the implementation code because you have a disproportionate number of tests to update. Tests should help you refactor with confidence, rather than be a large burden that impedes your work on the code being tested.
If the duplication is in fixture set up, consider making more use of the
setUp
method or providing more (or more flexible) Creation Methods.If the duplication is in the code manipulating the SUT, then ask yourself why multiple so-called “unit” tests are exercising the exact same functionality.
If the duplication is in the assertions, then perhaps you need some Custom Assertions. For example, if multiple tests have a string of assertions like:
Then perhaps you need a single
assertPersonEqual
method, so that you can writeassertPersonEqual(Person('Joe', 'Bloggs', 23), person)
. (Or perhaps you simply need to overload the equality operator onPerson
.)As you mention, it is important for test code to be readable. In particular, it is important that the intent of a test is clear. I find that if many tests look mostly the same, (e.g. three-quarters of the lines the same or virtually the same) it is hard to spot and recognise the significant differences without carefully reading and comparing them. So I find that refactoring to remove duplication helps readability, because every line of every test method is directly relevant to the purpose of the test. That's much more helpful for the reader than a random combination of lines that are directly relevant, and lines that are just boilerplate.
That said, sometimes tests are exercising complex situations that are similiar but still significantly different, and it is hard to find a good way to reduce the duplication. Use common sense: if you feel the tests are readable and make their intent clear, and you're comfortable with perhaps needing to update more than a theoretically minimal number of tests when refactoring the code invoked by the tests, then accept the imperfection and move on to something more productive. You can always come back and refactor the tests later, when inspiration strikes!
您可以使用几种不同风格的测试实用方法来减少重复。
与生产代码相比,我更能容忍测试代码中的重复,但有时我对此感到沮丧。 当您更改类的设计并且必须返回并调整 10 种不同的测试方法(所有这些方法都执行相同的设置步骤)时,这是令人沮丧的。
You can reduce repetition using several different flavours of test utility methods.
I'm more tolerant of repetition in test code than in production code, but I have been frustrated by it sometimes. When you change a class's design and you have to go back and tweak 10 different test methods that all do the same setup steps, it's frustrating.
我喜欢 rspec 因为这个:
它有两件事可以帮助 -
用于测试常见行为的共享示例组。
您可以定义一组测试,然后将其“包含”到您的实际测试中。
嵌套上下文。
您基本上可以为测试的特定子集(而不仅仅是类中的每个测试子集)提供“设置”和“拆卸”方法。
.NET/Java/其他测试框架越早采用这些方法越好(或者你可以使用IronRuby或JRuby来编写测试,我个人认为这是更好的选择)
I LOVE rspec because of this:
It has 2 things to help -
shared example groups for testing common behaviour.
you can define a set of tests, then 'include' that set in your real tests.
nested contexts.
you can essentially have a 'setup' and 'teardown' method for a specific subset of your tests, not just every one in the class.
The sooner that .NET/Java/other test frameworks adopt these methods, the better (or you could use IronRuby or JRuby to write your tests, which I personally think is the better option)
我认为测试代码需要与通常应用于生产代码类似的工程水平。 当然可以提出有利于可读性的论据,我同意这很重要。
然而,根据我的经验,我发现精心设计的测试更容易阅读和理解。 如果有 5 个测试,除了一个变量发生了变化以及最后的断言之外,每个测试看起来都相同,那么很难找到那个不同的项目是什么。 类似地,如果对其进行分解,以便只有正在更改的变量和断言可见,那么很容易立即弄清楚测试正在做什么。
在测试时找到正确的抽象级别可能很困难,但我觉得这是值得做的。
I feel that test code requires a similar level of engineering that would normally be applied to production code. There can certainly be arguments made in favor of readability and I would agree that's important.
In my experience, however, I find that well-factored tests are easier to read and understand. If there's 5 tests that each look the same except for one variable that's changed and the assertion at the end, it can be very difficult to find what that single differing item is. Similarly, if it is factored so that only the variable that's changing is visible and the assertion, then it's easy to figure out what the test is doing immediately.
Finding the right level of abstraction when testing can be difficult and I feel it is worth doing.
我认为更多重复的代码和可读的代码之间没有关系。 我认为你的测试代码应该和其他代码一样好。 如果做得好,非重复代码比重复代码更具可读性。
I don't think there is a relation between more duplicated and readable code. I think your test code should be as good as your other code. Non-repeating code is more readable then duplicated code when done well.
理想情况下,单元测试一旦编写出来就不会有太大变化,因此我倾向于可读性。
让单元测试尽可能离散也有助于使测试集中于它们所针对的特定功能。
话虽如此,我确实倾向于尝试重用我反复使用的某些代码片段,例如在一组测试中完全相同的设置代码。
Ideally, unit tests shouldn't change much once they are written so I would lean towards readability.
Having unit tests be as discrete as possible also helps to keep the tests focused on the specific functionality that they are targeting.
With that said, I do tend to try and reuse certain pieces of code that I wind up using over and over, such as setup code that is exactly the same across a set of tests.
我同意。 这种权衡是存在的,但在不同的地方有所不同。
我更有可能重构重复的代码来设置状态。 但不太可能重构实际执行代码的测试部分。 也就是说,如果执行代码总是需要几行代码,那么我可能会认为这是一种味道,并重构被测试的实际代码。 这将提高代码和测试的可读性和可维护性。
I agree. The trade off exists but is different in different places.
I'm more likely to refactor duplicated code for setting up state. But less likely to refactor the part of the test that actually exercises the code. That said, if exercising the code always takes several lines of code then I might think that is a smell and refactor the actual code under test. And that will improve readability and maintainability of both the code and the tests.
Jay Fields 创造了这样一句话:“DSL 应该是 DAMP,而不是 DRY”,其中 DAMP 表示描述性且有意义的短语。 我认为这同样适用于测试。 显然,太多的重复是不好的。 但不惜一切代价消除重复的情况更糟。 测试应该充当揭示意图的规范。 例如,如果您从几个不同的角度指定相同的特征,那么就会出现一定数量的重复。
Jay Fields coined the phrase that "DSLs should be DAMP, not DRY", where DAMP means descriptive and meaningful phrases. I think the same applies to tests, too. Obviously, too much duplication is bad. But removing duplication at all costs is even worse. Tests should act as intent-revealing specifications. If, for example, you specify the same feature from several different angles, then a certain amount of duplication is to be expected.
“重构它们以使它们更加干燥——每个测试的意图不再明确”
听起来你在重构时遇到了困难。 我只是猜测,但如果结果不太清楚,这是否意味着您仍然需要做更多工作才能拥有相当优雅且完全清晰的测试?
这就是为什么测试是 UnitTest 的子类——这样您就可以设计正确、易于验证且清晰的良好测试套件。
在过去,我们有使用不同编程语言的测试工具。 设计令人愉快的、易于使用的测试是很难(或不可能)的。
无论您使用什么语言,您都拥有 Python、Java、C# 的全部功能,因此请好好使用该语言。 您可以获得清晰且不太冗余的美观测试代码。 没有权衡。
"refactored them to make them more DRY--the intent of each test was no longer clear"
It sounds like you had trouble doing the refactoring. I'm just guessing, but if it wound up less clear, doesn't that mean you still have more work to do so that you have reasonably elegant tests which are perfectly clear?
That's why tests are a subclass of UnitTest -- so you can design good test suites that are correct, easy to validate and clear.
In the olden times we had testing tools that used different programming languages. It was hard (or impossible) to design pleasant, easy-to-work with tests.
You have the full power of -- whatever language you're using -- Python, Java, C# -- so use that language well. You can achieve good-looking test code that's clear and not too redundant. There's no trade-off.