计算机科学和Python编程导论(六) 测试与调试

基本概念

测试指通过运行程序以确定它是否按照预期工作。

调试则指修复已知的未按预期工作的程序。

测试和调试的关键就是将程序分解成独立的部件,可以在不受其他部件影响的情况下实现、测试和调试。

关于测试,最重要的是清楚它的目的是证明错误的存在,而不是证明程序没有错误。

测试的关键就是找到极有可能产生错误答案的一组输入,可以称之为测试套件

找到测试套件的关键是,对所有可能的输入空间进行分区,将其划分为对程序正确性提供相同信息的多个子集,然后构建测试套件,使其包含来自每个分区的至少一个输入。

如果使用来自每个子集的至少一个值对函数实现进行测试,就非常有可能暴露可能存在的错误。

基于代码探索路径的启发式方法称为白盒测试

基于规范探索路径的启发式方法称为黑盒测试

如果一个白盒测试套件可以测试程序中所有潜在路径,那我们就可以认为它是路径完备的。一般来说,路径完备不可能达成,因为这取决于程序中循环的次数和递归的深度。

白盒测试提供的一些经验准则

 测试所有if语句的所有分支。
 必须测试每个except子句。
 对于每个for循环,需要以下测试用例:

  1. 未进入循环(例如,如果使用循环遍历列表中的所有元素,则必须测试空列表);
  2. 循环体只被执行一次;
  3. 循环体被执行多于一次;

 对于每个while循环:

  1. 包括上面for循环中的所有用例;
  2. 还要包括对应于所有跳出循环的方式的测试用例。例如,对于以while len(L) > 0 and
    not L[i] == e开始的循环,测试用例应该包括因为len(L)不大于0和因为L[i] == e
    而跳出循环的情况。

 对于递归函数,测试用例应该包括函数没有递归调用就返回、只执行一次递归调用和执
行多次递归调用的情况。

测试一般分为两个阶段。第一个阶段称为单元测试,第二个阶段称为集成测试。

第一个阶段称为单元测试。在这个阶段中,测试者构建并执行测试,
用来确定代码的每个独立单元(例如,函数)是否正常工作

第二个阶段称为集成测试,用来确
定整个程序能否按预期运行。

在工业界,测试过程通常是高度自动化的。测试者不会坐在终端前面手动输入用例并检查输出。他们会使用测试驱动程序

显性错误有明显的表现,如程序崩溃或运行时间异常长(可能永不停止)

隐性错误没有明显的表现,程序会正常结束,不出任何问题——除了给出一个错误答案

持续性错误在程序每次使用相同的输入运行时都会发生

间歇性错误仅在某些时候出现,即使程序使用相同输入并在相同条件下运行

优秀的程序员编写程序时,会尽量使程序错误是显性的和持续性的,这种编程方式通常称为防御性编程

多数程序员认为最重要的调试工具是print语句

如果将调试看作一个搜索过程,那么每次实验就要尽力缩减搜索空间。

缩减搜索空间的一种方法是,设计一个实验,确定代码的一个具体区域是否是造成某个问题的原因。另外一种缩减搜索空间的方法是,减少导致错误出现所需的测试数据量。

系统地缩减搜索空间,最好的方法是执行二分查找。先找出代码中间点,然后设计一个实验,确定是否因为中间点前面存在问题才导致程序出现这种症状

调试遇到困难时,我们该怎么做呢?

 排除常见错误。例如,看看你是否犯了以下错误:

  1. 以错误的顺序向函数传递实参;
  2. 拼错一个名称,如将大写字母写成小写;
  3. 变量重新初始化失败;
  4. 检验两个浮点数是否相等(==),而不是近似相等(请记住,浮点数的运算与学校里学
    的运算不一样);
  5. 在应该检验对象相等(如id(L1) == id(L2))的时候,检验值相等(例如,使用表达式 L1 == L2比较两个列表);
  6. 忘记了一些内置函数具有副作用;
  7. 忘记使用()将对function类型对象的引用转换为函数调用;
  8. 意外地创建了一个别名;
  9. 其他一些你常犯的错误。

 不要问自己为什么程序没有按照你的想法去做,而要问自己程序为什么像现在这样做。后者应该更容易回答,要想弄清楚如何修复程序,这可能是一个很好的开始。
 记住,错误可能不在你认为会出错的地方。如果在那里,你早就应该发现它了。确定错误位置的一种实用方法是,看看那些你认为不会出错的地方。
 试着向其他人解释程序的问题。每个人都会有盲点。经常有这样的情况,试图向别人解释问题的时候,你会突然发现自己忽略的地方。向其他人解释为什么程序中某个地方不会出现错误是个很好的选择。
 不要盲目相信任何书面上的东西。特别是,不要相信文档。代码行为可能与注释不一样。
 暂停调试,开始编写文档。这会帮助你从不同视角接近问题所在。
 出去散散步,明天接着做。这可能意味着与你坚持工作相比,修复问题的时间要晚一些,但花费的总时间会大大减少。也就是说,我们使用时间上的一点延迟换取了效率上的大幅提升。(同学们,开始习题集中的编程练习吧,宁早勿晚,这是个绝好的理由!)

我们的目标不是修复一个错误,而是快速有效地得到一个没有错误的程序。你应该扪心自问,这个错误能够解释所有观测到的症状,还是只是冰山一角。如果是后者,最好将对这个错误的处理与其他修改结合考虑。

猜你喜欢

转载自blog.csdn.net/Datawhale/article/details/81194197