断言代码太多了吗?


19

我真的爱上了单元测试和TDD-我被测试感染了。

但是,单元测试通常用于公共方法。有时候,尽管我确实也必须在私有方法中测试一些假设-断言,因为其中一些是“危险的”,并且重构无济于事。(我知道,测试框架允许测试私有方法)。

因此,我的习惯是私有方法的第一行和最后一行都是断言。

但是,我注意到我倾向于“肯定”地在公共方法(以及私有方法)中使用断言。因为公共方法假设是由单元测试框架从外部进行测试的,所以这可能是“测试重复”吗?

有人会认为太多的断言是代码的味道吗?


1
这些断言在发布时是打开还是关闭?
jk。

我主要使用必须手动启用断言的Java。
弗洛伦斯·塞莱

刚刚发现这个“通过跟踪Bug断言提高生产力” programmers.stackexchange.com/a/16317/32342
Florents Tselai

您如何定义“太多”?
haylem 2012年

@haylem如果另一个程序员读了代码并说“我对这些声明感到厌倦”,则存在“太多”的声明。
弗洛伦斯·谢莱

Answers:


26

这些断言对于检验您的假设确实很有用,但它们还有另一个真正重要的目的:文档。任何使用公共方法的读者都可以阅读断言,从而快速确定前后条件,而无需查看测试套件。出于这个原因,我建议您保留这些断言是出于文档原因,而不是出于测试原因。从技术上讲,您正在复制这些断言,但是它们有两个不同的用途,并且在这两个方面都非常有用。

将它们保持为断言比仅使用注释更好,因为它们在运行时都会主动检查假设。


2
很好点!
弗洛伦斯·谢莱

1
断言是文档,但这并不能使它们重复合法。相反,如果似乎不止一次需要相同的断言,它甚至会引起对测试质量的怀疑。
Yam Marcovic 2012年

1
@YamMarcovic我认为这是可以接受的,因为两个“重复的”代码段服务于两个不同且截然不同的目的。我认为在重复的情况下在两个位置放置它们很有用。在方法中包含断言对于文档来说是无价的,并且测试显然需要它们。
Oleksi 2012年

2
对我而言,代码断言是对单元测试断言的补充。您不会拥有100%的测试覆盖率,并且代码中的断言将帮助您更快地缩小失败的单元测试的范围。
sebastiangeiger 2012年

15

听起来您正在尝试手动执行按合同设计

进行DbC是个好主意,但是您至少应该考虑切换到本身支持它的语言(例如Eiffel),或者至少使用平台的合同框架(例如Microsoft .NET的代码合同)是相当不错的,可以说是最复杂的合同框架,甚至比Eiffel还强大。这样,您可以更好地利用合同的力量。例如,使用现有框架,您可以在文档或IDE中显示合同(例如,.NET的代码合同显示在IntelliSense中,并且,如果您使用VS Ultimate,则甚至可以在编译时由自动定理证明器进行静态检查),您进行键入;同样,许多Java合同框架都有JavaDoc doclet,它们会自动将合同提取到JavaDoc API文档中)。

即使事实证明,在您所处的环境中,没有什么可以替代手动进行操作的,您现在至少知道它被称为什么,并且可以阅读它。

简而言之,如果您确实在做DbC,即使您不知道它,那么这些断言也很好。


4

每当您无法完全控制输入参数时,最好先进行简单错误测试。例如,失败为null。

这不是你的测试的重复,因为他们应该测试的代码失败适当地给予错误的输入参数,然后记录

我不认为您应该对返回参数进行断言(除非您明确希望读者理解一个不变式)。这也是单元测试的工作。

就我个人而言,我不喜欢assertJava中的语句,因为可以将其关闭,这是一种错误的安全性。


1
+1表示“我不喜欢Java中的assert语句,因为可以将其关闭,这是错误的安全性。”
弗洛伦斯·谢莱

3

我认为,在公共方法中使用断言更为重要,因为您无法控制输入,并且更有可能打破假设。

测试输入条件应采用所有公共和受保护的方法进行。如果将输入直接传递给私有方法,则测试其输入可能是多余的。

测试输出条件(例如对象状态或返回值!= null)应在内部方法(例如私有方法)中进行。如果将输出直接从私有方法传递到公共方法的输出而没有其他计算或内部状态更改,则测试公共方法的输出条件可能是多余的。

我确实同意Oleksi的观点,但是冗余可以提高可读性,也可以提高可维护性(如果将来不再需要直接分配或返回)。


4
如果公共接口的用户(调用者)可能导致错误条件或无效的参数条件,则应给予完全适当的错误处理。这意味着格式化对最终用户有意义的错误消息,并附带错误代码,日志记录,并尝试恢复数据或恢复到正常状态。用断言代替正确的错误处理将使代码不那么健壮,更难解决。
rwong 2012年

断言可以(并且在许多情况下应该包括)记录和抛出异常(或C--中的错误代码)。
丹尼·瓦罗德

3

很难在语言上与该问题无关,因为断言和“正确”错误/异常处理的实现方式的细节与答案有关。根据我对Java和C ++的了解,这是我的0.02美元。

在私有方法断言是一件好事,假设你不太过火,并把它们无处不在。如果将它们放在非常简单的方法上或反复检查诸如不可变字段之类的东西,那么就不必要地弄乱了代码。

通常最好避免公开方法中的断言。您仍然应该做一些事情,例如检查是否违反了方法协定,但是如果这样,您应该抛出带有有意义消息的适当类型的异常,并在可能的情况下还原状态,等等。(@ rwong称为“完整”适当的错误处理方法”)。

一般来说,您应该只使用断言来帮助您进行开发/调试。您不能假设使用您的API的人在使用您的代码时甚至会启用断言。尽管它们确实在帮助编写代码方面有一定用处,但通常还是有更好的方法来记录相同的内容(即方法文档,异常)。


2

除了(大多数)例外答案之外,还有很多断言和重复的另一个原因是,您不知道在整个生命周期中将如何,何时或由谁来修改类。如果您要编写扔掉的代码,这些代码将在一年后失效,那么您就不必担心了。如果您要编写将在20多年后使用的代码,则外观将大不相同-并且您所做的假设可能不再有效。做出更改的人将感谢您的主张。

同样,并不是所有的程序员都是完美的-再次,断言意味着那些“滑坡”不会传播得太远。

不要低估这些断言(以及对假设的其他检查)对降低维护成本的影响。


0

拥有重复的断言本质上并没有错,但是拥有“仅仅为了确保”的断言并不是最好的。实际上,可以归结为每个测试试图完成的目标。仅声明绝对需要的内容。如果测试使用Moq来验证是否调用了方法,则在调用该方法之后发生的情况并不重要,该测试仅与确保调用该方法有关。

如果每个单个单元测试都具有相同的断言集(除了一个或两个之外),那么当您重构时,所有测试都可能由于完全相同的原因而失败,而没有向您展示重构的真正影响。您可能最终会遇到以下情况:您运行所有测试,但都失败了,因为每个测试都具有相同的断言,您可以修复该故障,然后再次运行测试,它们由于其他原因而失败,您可以修复该故障。重复直到完成。

还考虑维护您的测试项目。当您添加新参数或对输出进行如此微调时,会发生什么情况,您是否必须返回一堆测试并更改断言或添加断言?而且,根据您的代码,您可能最终不得不进行更多设置才能确保所有断言都能通过。

我可以理解想要在单元测试中包含重复断言的吸引力,但这确实是过分的。我沿着第一个测试项目走了同样的路。我之所以放弃,是因为仅靠维护就使我发疯。


0

但是,单元测试用于公共方法。

如果您的测试框架允许访问器,那么对私有方法进行单元测试也是一个好主意(IMO)。

有人会认为太多的断言是代码的味道吗?

我做。断言是可以的,但是您的第一个目标应该是使之成为现实,以使这种情况甚至不可能发生;不仅如此,这不会发生。


-2

这是不好的做法。

您不应该测试公共/私有方法。您应该对整个课程进行测试。只要确保您具有良好的覆盖范围即可。

如果您以经验法则(单独)测试每种方法的(原始)方法,那么将来将很难重构您的类。这是TDD使您能够做到的最好的事情之一。

此外,它重复的。此外,这表明代码编写者并不真正知道自己在做什么。而有趣的是,你做到了

有些人对测试的重视程度不及生产代码。他们不应该。如果有的话,我的经验表明,甚至应该更加谨慎地对待测试。他们值得。


-1整体上对一个类进行单元测试可能很危险,因为您每次测试最终要测试多个对象。这使测试变得非常脆弱。您应该一次测试一种行为,这通常相当于每次测试仅测试一种方法。
Oleksi 2012年

关于“它表明代码的编写者并不真正知道他在做什么”。未来的开发人员可能不清楚特定方法的作用。在这种情况下,以断言的形式具有前后条件对于理解一段代码可能是无价的。
Oleksi 2012年

2
@Oleksi好的测试是关于有效的使用场景,而不是断言每个最小的无关细节。冗余断言确实是一种代码气味,因为其意图不明确,并且仅仅是防御性编程的另一个不好的例子。
Yam Marcovic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.