我真的爱上了单元测试和TDD-我被测试感染了。
但是,单元测试通常用于公共方法。有时候,尽管我确实也必须在私有方法中测试一些假设-断言,因为其中一些是“危险的”,并且重构无济于事。(我知道,测试框架允许测试私有方法)。
因此,我的习惯是私有方法的第一行和最后一行都是断言。
但是,我注意到我倾向于“肯定”地在公共方法(以及私有方法)中使用断言。因为公共方法假设是由单元测试框架从外部进行测试的,所以这可能是“测试重复”吗?
有人会认为太多的断言是代码的味道吗?
我真的爱上了单元测试和TDD-我被测试感染了。
但是,单元测试通常用于公共方法。有时候,尽管我确实也必须在私有方法中测试一些假设-断言,因为其中一些是“危险的”,并且重构无济于事。(我知道,测试框架允许测试私有方法)。
因此,我的习惯是私有方法的第一行和最后一行都是断言。
但是,我注意到我倾向于“肯定”地在公共方法(以及私有方法)中使用断言。因为公共方法假设是由单元测试框架从外部进行测试的,所以这可能是“测试重复”吗?
有人会认为太多的断言是代码的味道吗?
Answers:
这些断言对于检验您的假设确实很有用,但它们还有另一个真正重要的目的:文档。任何使用公共方法的读者都可以阅读断言,从而快速确定前后条件,而无需查看测试套件。出于这个原因,我建议您保留这些断言是出于文档原因,而不是出于测试原因。从技术上讲,您正在复制这些断言,但是它们有两个不同的用途,并且在这两个方面都非常有用。
将它们保持为断言比仅使用注释更好,因为它们在运行时都会主动检查假设。
听起来您正在尝试手动执行按合同设计。
进行DbC是个好主意,但是您至少应该考虑切换到本身支持它的语言(例如Eiffel),或者至少使用平台的合同框架(例如Microsoft .NET的代码合同)是相当不错的,可以说是最复杂的合同框架,甚至比Eiffel还强大。这样,您可以更好地利用合同的力量。例如,使用现有框架,您可以在文档或IDE中显示合同(例如,.NET的代码合同显示在IntelliSense中,并且,如果您使用VS Ultimate,则甚至可以在编译时由自动定理证明器进行静态检查),您进行键入;同样,许多Java合同框架都有JavaDoc doclet,它们会自动将合同提取到JavaDoc API文档中)。
即使事实证明,在您所处的环境中,没有什么可以替代手动进行操作的,您现在至少知道它被称为什么,并且可以阅读它。
简而言之,如果您确实在做DbC,即使您不知道它,那么这些断言也很好。
每当您无法完全控制输入参数时,最好先进行简单错误测试。例如,失败为null。
这不是你的测试的重复,因为他们应该测试的代码失败适当地给予错误的输入参数,然后记录该。
我不认为您应该对返回参数进行断言(除非您明确希望读者理解一个不变式)。这也是单元测试的工作。
就我个人而言,我不喜欢assert
Java中的语句,因为可以将其关闭,这是一种错误的安全性。
我认为,在公共方法中使用断言更为重要,因为您无法控制输入,并且更有可能打破假设。
测试输入条件应采用所有公共和受保护的方法进行。如果将输入直接传递给私有方法,则测试其输入可能是多余的。
测试输出条件(例如对象状态或返回值!= null)应在内部方法(例如私有方法)中进行。如果将输出直接从私有方法传递到公共方法的输出而没有其他计算或内部状态更改,则测试公共方法的输出条件可能是多余的。
我确实同意Oleksi的观点,但是冗余可以提高可读性,也可以提高可维护性(如果将来不再需要直接分配或返回)。
很难在语言上与该问题无关,因为断言和“正确”错误/异常处理的实现方式的细节与答案有关。根据我对Java和C ++的了解,这是我的0.02美元。
在私有方法断言是一件好事,假设你不太过火,并把它们无处不在。如果将它们放在非常简单的方法上或反复检查诸如不可变字段之类的东西,那么就不必要地弄乱了代码。
通常最好避免公开方法中的断言。您仍然应该做一些事情,例如检查是否违反了方法协定,但是如果这样,您应该抛出带有有意义消息的适当类型的异常,并在可能的情况下还原状态,等等。(@ rwong称为“完整”适当的错误处理方法”)。
一般来说,您应该只使用断言来帮助您进行开发/调试。您不能假设使用您的API的人在使用您的代码时甚至会启用断言。尽管它们确实在帮助编写代码方面有一定用处,但通常还是有更好的方法来记录相同的内容(即方法文档,异常)。
拥有重复的断言本质上并没有错,但是拥有“仅仅为了确保”的断言并不是最好的。实际上,可以归结为每个测试试图完成的目标。仅声明绝对需要的内容。如果测试使用Moq来验证是否调用了方法,则在调用该方法之后发生的情况并不重要,该测试仅与确保调用该方法有关。
如果每个单个单元测试都具有相同的断言集(除了一个或两个之外),那么当您重构时,所有测试都可能由于完全相同的原因而失败,而没有向您展示重构的真正影响。您可能最终会遇到以下情况:您运行所有测试,但都失败了,因为每个测试都具有相同的断言,您可以修复该故障,然后再次运行测试,它们由于其他原因而失败,您可以修复该故障。重复直到完成。
还考虑维护您的测试项目。当您添加新参数或对输出进行如此微调时,会发生什么情况,您是否必须返回一堆测试并更改断言或添加断言?而且,根据您的代码,您可能最终不得不进行更多设置才能确保所有断言都能通过。
我可以理解想要在单元测试中包含重复断言的吸引力,但这确实是过分的。我沿着第一个测试项目走了同样的路。我之所以放弃,是因为仅靠维护就使我发疯。
这是不好的做法。
您不应该测试公共/私有方法。您应该对整个课程进行测试。只要确保您具有良好的覆盖范围即可。
如果您以经验法则(单独)测试每种方法的(原始)方法,那么将来将很难重构您的类。这是TDD使您能够做到的最好的事情之一。
此外,它是重复的。此外,这表明代码编写者并不真正知道自己在做什么。而有趣的是,你做到了。
有些人对测试的重视程度不及生产代码。他们不应该。如果有的话,我的经验表明,甚至应该更加谨慎地对待测试。他们值得。