断言或单元测试更重要吗?


51

断言测试和单元测试都充当代码库的文档,并且是发现错误的一种手段。主要区别在于,断言充当健全性检查并查看实际输入,而单元测试则在特定的模拟输入上运行,并且是针对单个明确定义的“正确答案”的测试。使用断言与单元测试作为验证正确性的主要方法有哪些相对优点?您认为应该重点强调哪个?


4
我非常喜欢这个主意,以至于我将该主题分配给我的软件测试和质量保证课程。:-)
Macneil 2010年

另一个问题是:您应该对断言进行单元测试吗?;)
mojuba 2010年

Answers:


43

断言对于告诉您程序的内部状态很有用。例如,您的数据结构具有有效状态,例如,Time数据结构将不包含的值25:61:61。断言检查的条件是:

  • 确保呼叫者遵守合同的前提条件,

  • 后置条件,确保被叫方遵守合同,以及

  • 不变量,可确保函数返回后数据结构始终具有某些属性。不变式是先决条件和后置条件的条件。

单元测试对于告诉您模块的外部行为很有用。调用Stackpush()方法后,您的状态可能保持一致,但是如果调用3次后堆栈的大小没有增加3,则这是一个错误。(例如,在这种情况下,错误的push()实现仅检查断言并退出)。

严格来说,断言和单元测试之间的主要区别在于,单元测试具有测试数据(使程序运行的值),而断言则没有。也就是说,您可以自动执行单元测试,而不能对断言说相同。为了便于讨论,我假设您正在谈论在高阶功能测试(执行整个程序,而不像单元测试那样驱动模块)的上下文中执行程序。如果您不打算将自动化功能测试作为“查看实际输入”的手段,那么显然价值在于自动化,因此单元测试将是成功的。如果您在(自动)功能测试的上下文中谈论此问题,请参见下文。

被测试的内容可能会有一些重叠。例如,Stack的后置条件实际上可能断言堆栈大小增加了一个。但是在该断言中可以执行的操作有一些限制:是否还应该检查top元素是刚刚添加的元素?

两者的目标都是提高质量。对于单元测试,目标是发现错误。对于断言,目标是通过观察无效的程序状态,使它们更容易调试。

请注意,两种技术都无法验证正确性。实际上,如果您进行单元测试的目的是验证程序是否正确,那么您很可能会提出无用的测试,而您知道该测试会起作用。这是一种心理效应:您将尽一切努力实现自己的目标。如果您的目标是发现错误,那么您的活动将反映出来。

两者都很重要,并且都有自己的目的。

[作为断言的最后注解:要获得最大价值,您需要在程序的所有关键点使用它们,而不是几个关键功能。否则,问题的原始来源可能已经被掩盖,并且如果不进行数小时的调试就很难发现。]


7

在谈论断言时,请记住,只需轻按一下即可将其关闭。

一个非常错误的断言示例:

char *c = malloc(1024);
assert(c != NULL);

为什么这样不好?因为如果由于定义了NDEBUG之类而跳过了该断言,则不会执行任何错误检查。

单元测试(可能)只是对上面的代码进行段错误。当然,它是通过告诉您出了点问题来完成工作的,还是这样做了?malloc()测试失败的可能性有多大?

当程序员需要暗示没有“正常”事件会导致断言触发时,断言用于调试目的。malloc()失败,的确是正常事件,因此不应断言。

在许多其他情况下,使用断言而不是充分处理可能出错的事情。这就是为什么断言获得不良声誉的原因,以及诸如Go之类的语言不包含断言的原因。

单元测试旨在告诉您您所做的更改何时破坏了其他内容。它们的设计目的是在您每次构建时(大多数情况下)使您免于遍历程序的每个功能(但是,测试人员对于发布而言重要)。

两者之间确实没有任何明显的相关性,只是两者都告诉您出了点问题。可以将断言视为您正在处理的事情的断点,而不必使用调试器。可以将单元测试视为可以告诉您是否破坏了您不在进行的工作的东西。


3
这就是为什么断言总是被认为是真实的陈述,而不是错误测试的原因。
Dominique McDonnell

@DominicMcDonnell好吧,“应该是真实的”陈述。我有时会断言要避开编译器怪癖,例如某些内置了有问题的abs()的gcc版本。要记住的重要一点是生产版本无论如何都应该将其关闭。
蒂姆·波斯特

在这里,我认为生产代码是需要断言的地方,因为在生产中您将获得您认为不可能的输入。生产是排除所有最严重的错误的地方。
Frank Shearar 2010年

@弗兰克·希勒(Frank Shearar),非常真实。在生产中最早检测到的错误状态下,您仍然应该努力进行失败。当然,用户会抱怨,但这是确保错误得以修复的唯一方法。而且,在以后的几个函数调用中取消对null的引用时,获得blah为零要比内存异常好得多。
Dominique McDonnell

为什么处理典型但通常不常见(在用户眼中)的问题而不是断言不应该发生这些问题,为什么更好呢?我没有意识到这种智慧。当然,断言一个质数生成器会返回一个质数,这需要一些额外的工作,这在调试版本中是可以预期的。断言某种语言可以本地测试的东西是愚蠢的。没有将这些测试包装在另一个开关中,该开关在发布后几个月就可以关闭,甚至更愚蠢。
蒂姆·波斯特

5

它们都是用于帮助提高所构建系统的整体质量的工具。在很大程度上取决于您使用的语言,所构建的应用程序的类型以及您在哪里花费的时间最多。更不用说您对此有一些看法。

首先,如果您使用的是不带assert关键字的语言,则不能使用断言(至少不能使用我们在此所说的方式)。很长时间以来,Java没有assert关键字,许多语言仍然没有关键字。然后,单元测试变得相当重要。在某些语言中,仅当设置了标志时才运行断言(此处再次使用Java)。当保护不总是存在时,它不是一个非常有用的功能。

有一种流派说,如果您“断言”某件事,您不妨编写一个if/ throw有意义的异常块。这种思考过程来自放置在方法开头的许多断言,以确保所有值都在边界内。测试您的前提条件是拥有预期后置条件的非常重要的部分。

单元测试是必须编写和维护的额外代码。对许多人来说这是一个缺点。但是,使用当前的大量单元测试框架,您可以使用较少的代码生成大量的测试条件。参数化测试和“理论”将对大量数据样本执行相同的测试,这些数据样本可能会发现一些难以发现的错误。

我个人发现,单元测试比散布断言要多得多,但这是因为我大部分时间都在开发平台(Java / C#)。其他语言具有更强大的断言支持,甚至“按合同设计”(见下文)也提供了更多的保证。如果我使用这些语言之一,那么我可能会比单元测试更多地使用DBC。

http://en.wikipedia.org/wiki/Design_by_contract#Languages_with_native_support

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.