测试是程序中的一个核心概念,从 if 语句的表达式部分就可以见到测试概念的应用与体现。测试是一个过程,是为了检查一个程序的正确性,当然同时也会有另外一个过程,那就是调试(debugging)。调试这个过程是为了追踪一个程序执行时发生了哪些错误。
测试与调试在开发期间是最耗时的工作,虽然都是保证一个软件质量的工作,但却不同。
第一测试:
需要一个详细谨慎的测试计划,这是在写一个程序时不可缺少的一部分内容。虽然实际情况可能与计划有出入,但是能够保证你写程序时有依据可查。
道理上验证一个软件的正确性都是想尽可能针对所有的输入来做测试,但常常是不现实的。我们要针对某一些输入来测试执行程序时的状况,也就是对于输入做分类,会分成不同的子集。
至少我们应该确保一个类的每个方法都要经过测试,这里我们会得到一份方法覆盖率结果。好一点的测试会针对每个语句来进行,这样会得到语句覆盖率结果。
程序常常会在具体的输入情况中导致执行失败,所以要小心谨慎地识别和进行测试。例如,当测试一个方法时,该方法对输入的整数做排序,那么我们会在输入环节考虑一些情况:
1.如果输入时的长度为零,也就是没有任何东西。
2.输入只有一项内容时。
3.输入的内容都是一样的时候。
4.输入的内容已经是排好序的。
5.输入的内容是倒序排列的。
对于一些程序的具体输入来说,也会考虑程序使用的具体结构情况。例如我们用 Python 列表来存储数据的话,我们要考虑索引范围情况会不会越界。因为在插入或删除列表项时要确保正确地完成处理。
当然使用测试套件也是不可缺少的,因为可以提供一些优势来运行程序,尤其在大型的随机生成输入数据方面。在 Python 中会有一个 random 模块,提供了许多有价值的随机数生成,以及随机排序功能。
那么对于一个程序中的类和函数之间的依赖关系,会产生一种等级现象。比如在等级机制中,如果组件 A 依赖组件 B 的话,组件 A 位于组件 B 的上级,又或者当函数 A 调用函数 B 的时候,或者函数 A 需要的一个参数来自一个类的实例。那么测试的思路会有2个,那就是:从上到下,和从下到上。二者的区别在于测试的组件顺序不一样。
从上到下的测试通常体现在 stub 测试中,我们称为存根测试,这是一种测试技术。采用一个存根来代替低层组件,存根的作用是模拟低层组件的原始功能。例如一个函数 A 调用函数 B 是为了得到一个文件的首行内容的话,那么测试 A 的时候,我们可以用一个 stub 函数来代替实际对象函数 B,那么 stub 函数就是返回一个固定的字符串即可。
从下到上执行的是从低层组件到高层组件的测试顺序。例如低层的函数不会涉及其它函数,那么就先要进行对低层函数的测试,然后逐步的测试只会调用低层函数的函数,以此类推。
作为一个类来说,也是类似的,先要测试的类是不依赖任何其它类,然后才会测试与此类有关联的类。
上面所说的这些测试内容常统称为单元测试,在较大的软件项目中会对单独的具体组件功能性进行测试。如果使用正确的话,这种单元测试技术会比测试那些出了问题的组件要好。因为较低层的组件都是经过测试的,测试那些高层组件就会更容易发现问题。
Python 提供了许多自动化测试支持。当函数或类都写在一个模块里的话,测试代码也可以写在这样的模块中。我们把测试代码写在模块的执行区域习语结构中。因为执行区域的代码只会在运行这个模块时执行,而导入模块时不会牵扯到这部分代码块。
更好一点的自动化单元测试会使用 unittest 模块,这是一种测试框架,可以对测试用例进行分组,从而形成更大的测试套件。而且也会提供一些执行测试套件、报告测试结果、分析测试结果的支持。
作为软件的维护来说,会使用回归测试,这会对以前的测试用例进行再次执行,从而确保软件中如果有代码变更的话,都可以发现来防止新 bug 出现在经过测试的组件上。
第二调试:
在 Python 中最直接的调试技术就是使用 print()函数来输出你想查看的变量值。这种方法有一个问题,那就是需要删除或注释掉 print 语句,这样才可以作为软件的最终发布版本。
更好的方法是让程序运行在调试器环境中,Python 提供了 pdb 标准库。调试器支持控制和监视一个运行的程序,最基本的功能就是在你的代码中插入一个断点,作为进入调试器环境的入口点。当程序执行在调试器环境中,会停在含有断点的位置上,那么你就可以查看执行过的变量值了。
Python 的 pdb 模块优势在于可以直接用在解释器环境里。一些其它的 IDE 软件也会提供一种图形化的调试器界面。