2015-06-03 207 views
1

这里是我的高层次,一般的问题:Java的断言,在比较/对比单元测试和异常

一个为什么要使用断言,而不是单元测试和异常处理?

为了澄清我的疑惑并限定我的顾虑,我会谈一谈我对Java断言和我的背景的了解,因为它与这一问题相关。所以,要开始,在本周之前,我真的不知道Java框架的断言,也不知道它们的功能,所以我绝对承认我很可能没有得到全部图像。话虽如此,我近十年来一直是Web开发,数据和API开发方面的专家,所以我并不完全天真。

从我读过的和讨论过的内容来看,断言通常在开发过程中使用,(几乎)在生产中总是关闭,并检查后期条件和类不变值;他们可以被用于其他事情,但这两件事似乎是他们当前的主要用法。它们构建在Java异常之上,是Error的子类型,并在违反时引发。关闭时,它们不会增加运行时间开销。但是,对我而言,它们看起来是多余的,特别是在考虑良好的异常处理技术和良好的单元测试实践时。我没有看到任何情况下,断言会提醒程序员注意异常处理或单元测试无法解决的错误或问题。

遵循这一思路,他们似乎在创造代码混乱。如果他们不打算在生产环境中运行并充当开发援助,那么他们在生产代码中的存在就有点反模式:你有单独的源文件夹用于测试和测试资源,而你编写可测试的代码,而是而不是代码严格的测试。每当我发现自己编写的代码只是为了测试,就会提醒我设计不佳,然后我退后一步,重新设计我正在做的事情,以便我的代码可以测试。在我看来,这是这种范例被侵犯的一个例子。无论存在什么断言,它都可以被try/catch块取代,如果/ then/else抛出了适当的异常,或者通过一组单元测试 - 至少,这就是我认为的方式。在生产代码中声明不会在生产过程中运行,这是令人困惑的。它也会干扰代码的目的和代码的功能。我发现一个测试可以评估相同的条件,断言会更加可读,因为它会被包含在一个测试中,该测试可以为该确切场景命名和记录。

其他人可以增加洞察这一思路?也许你同意 - 添加例子为什么或阐述我的推理。再说一遍,也许你认为我错了 - 陈述原因并提供例子来支持你的推理,或者提供反面的观点来看待我的观点。

谢谢大家!


资源我请教,供大家参考:

+0

一般来说,你可能是对的。你可以为每个“testing”assert编写一个JUnit测试,并且你可以用try-catch或者throw来代替每个“functional”assert。但是对于快速代码抽样,编写'assert'比编写完整的测试用例要容易。也许这就是Java'assert'关键字的利基...... – Turing85

+0

“用于快速代码抽样,比编写完整的测试用例更容易编写断言” - 比另一个更为正确,或者它们是否有不同/补充用例?在我看来,从测试和生产代码明显分离的角度来看,单元测试会更加正确,并且明确地抛出一个详细且恰当命名的异常会更清晰和更具可读性。在这一点上我只是在qu?? – liltitus27

+0

这正是我所说的*快速代码抽样*。单元测试更精细,代码更昂贵,以及'try-catch'块和propper异常处理。 – Turing85

回答

2

使用断言应该涵盖发生的不可能的不可能的条件。当不可能发生持续的时间时,最好大声死亡,并在后一点提供不正确的结果或不相关的例外情况。单元测试不是真正的替代品,因为你不能用它们来证明一些条件可以发生而不是

其中一个典型用途是switch声明。如果您确信case条款涵盖了所有可能的状态,则可以将default块保留为空,并且希望您的产品正确(并且在周围代码更改时您仍然是正确的),或者在其中放置断言,以便通知您如果你错了。

无论您是否决定使用Java的assert语句(可以按照您的说法关闭)或直接抛出AssertionError(这是无法关闭的东西)完全取决于您。

+0

“单元测试不是真正的替代方法,因为你不能用它们来证明某些条件不可能发生” - 我认为这是我没有把握的一个概念。一个断言如何证明一个单元测试不能发生某种情况?关于switch语句,为什么我不想明确地抛出InvalidArgumentException等,而不是assert,这可能会在生产中被关闭? – liltitus27

+0

我不是说你可以用断言来证明永远不会发生的事情(或者总是会发生的事情)。我建议使用断言表达这样的假设,以便以可见的方式对其进行反驳(与日志消息或评论相比)。 –

+0

Java的'InvalidArgumentException'表示客户端将无效值传递给您的代码。尽管由于编程错误,这些值来自您自己的代码,但这没有意义。 –

2

本主题是关于意见,所以我只是添加了一些关于该主题的想法。这些不包括所有方面,但可以补充他人的意见。

有时我使用断言来表达我在编写代码时做出的一些假设。简单的例子:我打电话给方法来获得一般可以是null例如通过名称找到用户,但在这种特殊情况下,这不会发生,例如,我正在注册。因此,我跳过代码“空检查”,但我要明确谁是永远之后(也许我在一个月:)),我认为这个选项

User user = findUser(userId); 
assert user != null : "we are in the middle of registration" 
user.confirmEmail(); 

很显然,我可以读我的代码使用简单的评论来做到这一点,但在代码混乱的时候并没有太大的坏处。

正如您已经提到的,第二种用例是检查不变量。 “学校示例”就是您正在实施合并排序的情况,并且在合并阶段期间,算法期望您要合并的两个子数组已经排序(按照前面的步骤)。

很明显,你会实现单元测试来检查排序是否正常工作,但这只能验证“端到端”的正确性。对于大多数情况来说这很好,但是当你正在实施它时,你会发现你的一个测试失败了。不幸的是,这个测试使用“大量输入”,并且所有“简单”测试都通过了,所以调试问题是不切实际的。由于合并类没有状态,只能用于递归和局部变量,所以不能从外部单元测试中反省状态。这是您可以使用断言来检查内部不变量的地方。一旦不变失败,你将得到堆栈跟踪(因此你发现在递归的第四级有一个问题),你可以将重要本地变量的实际状态添加到消息中,这样可以帮助你识别错误。这里断言是很好的,因为如果在生产期间对子阵列进行排序,您不想“重新检查”,因为这样的检查可能非常昂贵。

修复代码后,您可能会试图删除此类代码,但这可能取决于具体情况。我们为什么首先编写单元测试?因为如果我们重构,我们想确定我们是否没有破坏任何东西。如果你期望你可能想要重构代码,并且你所做的不变式可以保留下来,你可能希望保留它们以备将来使用。否则,您可以简单地删除它们或根本不使用断言,只需使用一些临时代码来查找问题。

+0

“很明显,我可以使用简单的评论来做到这一点,但在代码混乱的时候并没有太大的坏处。”我可以看到这一点。我最初的倾向是不同意,但退后一步,我可以看到,作为文档的功能,你是对的。我认为就评论而言,至少在某些情况下,这只是习惯将断言视为(类型)评论的问题。 – liltitus27