测试和 Debug 的若干建议


(翻译自 open courseware)

你只能通过测试找出程序的错误,但不能通过测试来证明程序正确。

测试关键是找出一系列输入,被称作 test suite,这些输入很有可能导致程序出错。

黑盒测试

黑盒测试就是不看源代码进行测试。 设计课程作业的老师为做题目的学生安排测试,这个测试就是黑盒测试。 黑盒测试的一个好处就是当代码实现变了时,测试用例不用变,因为黑盒测试是独立于源代码的。

白盒测试( glass-box testing )

相比黑盒测试,白盒测试更容易构建。因为黑盒测试是根据规格书写的,但规格书经常很粗糙和不完整,使得构造黑盒测试很难。

一个鉴别每个输入空间的方法是找到一系列测试用例覆盖程序代码的每个路径。那些覆盖全部路径的测试用例被称作是路径完全的( path-complete )。路径完全的测试用例最少要能覆盖源代码的每一行,甚至是每个语句。

单元测试和集成测试

测试经常分做单元测试和集成测试。单元测试就是测试相对独立的一小撮代码,集成测试则测试代码的整体表现。集成测试通常更难,因为代码整体表现测试要比独立的各个部分测试更难掌控。

很多软件公司都有单独的软件质量保证( SQA )小组。他们来保证软件是严格沿着既定目标开发的。在一些公司,开发小组负责单元测试,QA 小组负责集成测试。

在工业界,测试流程是高度自动化的,测试工程师不用坐在终端前键入输入检查输出。相反,他们使用测试驱动来达到以下目的: 建立调用测试程序所需的环境。 调用程序,使用先前定义好的或者自动生成的输入为参数进行测试。 保存调用结果。 判断测试结果是否可接受并生成报告。

在单元测试中,我们经常也需要建立测试桩。测试桩很有用途,因为它能让我们测试那些依赖还不存在的软件和硬件的代码。这能让程序员并行开发测试系统的各个部分。理想状态下,测试桩应该: 1,检测调用者提供的环境和参数是否可用。 2,根据规格书修改参数和全局变量 3,根据规格书返回值

自动化测试的一个魅力就是可以很容易地回归测试。在debug时程序员很容易在修补一处代码时导致另一处代码发生错误。无论做了一个多么小的改变,都需要重新运行测试。

Debugging

Good programmers try to write their programs in such a way that programming mistakes lead to bugs that are both overt and persistent. This is often called defensive programming.

如何debug

1,从已有的数据(测试结果和程序代码)开始。其中测试结果,应该不仅仅注重于那些暴露出 bug 的测试,还要检查那些看似正常的测试。通常试着理解为什么其中一个测试能通过,而其他的通不过是很有启发性的。

2,根据已有数据提出一个假想,它可以是“如果我把 x < y 修改为 x<=y,bug 就会消失”,也可以是宽泛的“我的问题是程序因为while循环的结束条件错误而陷入死循环”

3,设计一个可复现的试验,并运行它。它有可能会驳倒上文的假设。比如,你可以在 while 循环的前后各放一个 print 语句,如果它们总是成对输出,那么 while 循环是死循环这个假设就可以驳倒了。在运行试验前,就要对可能出现的结果进行分析,如果在试验后再做,就可能导致想法被引导到自己希望的方向。

4,最后,记录下你进行过哪些测试,这是重点。当你花费数个小时来跟踪一个难解的bug时,很容易忘掉自己进行过什么测试。

设计debug流程

重点就是对半搜索,比如写一个 50 行的 function,测试结果不是我们想要的。那么就从 25 行开始 debug,放一个 print(x),输出想要检查的变量。如果改变量的值不对,那么说明 bug 出现在25行之前,那么再在25/2=12行放置 print(x),如此重复数次直到找到bug为止。

当你找到 bug 以后你要做什么

当找到一个代码里的 bug,人们会急着想把这个 bug 修复掉。但最好慢一点。记着,你的目标不是修复某个 bug,而是要构造一个 bug-free 程序。

在修复一个 bug 之前,要注意这个修复是否会引发别的问题,是否会引入过度的复杂性?

要随时保证你可以回到起点。在你做出一系列修改之后,你要保证可以回到起始的版本。硬盘空间一般都是够用的,记得存储旧版本的程序。