测试和调试

415 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

测试和调试

我们讨厌提起这件事,但是潘格罗斯博士49。错了。我们不是生活在“所有可能世界中最好的”。有些地方下雨太少,有些地方下得太多。有些地方太冷,有些地方太热,有些夏天太热,冬天太冷。有时股市会下跌很多。有时作弊者确实会赢(参见休斯顿太空人队)。而且,令人讨厌的是,我们的程序在第一次运行时并不总是正常运行。

关于如何处理这最后一个问题的书已经写好了,从阅读这些书中可以学到很多东西。但是,为了向您提供一些提示,以帮助您按时完成下一个问题集,本章提供了对该主题的高度浓缩的讨论。虽然所有的编程示例都在Python中,但一般原则适用于使任何复杂的系统正常工作。

测试是运行程序以尝试并确定它是否按预期工作的过程。调试是尝试修复您已经知道无法按预期工作的程序的过程。测试和调试不是在生成程序后应该开始考虑的过程。优秀的程序员设计他们的程序的方式使它们更容易测试和调试。执行此操作的关键是将程序分解为单独的组件,这些组件可以独立于其他组件实现、测试和调试。在本书的这一点上,我们只讨论了一种模块化程序的机制,即函数。因此,就目前而言,我们所有的示例都将基于函数。当我们进入其他机制,特别是类时,我们将回到本章中介绍的一些主题。

使程序工作的第一步是让语言系统同意运行它 - 也就是说,消除语法错误和静态语义错误,这些错误可以在不运行程序的情况下检测到。如果你在编程中还没有超过这一点,那么你还没有为本章做好准备。花更多的时间在小程序上,然后回来。

测试

测试的目的是表明存在错误,而不是表明程序没有错误。引用Edsger Dijkstra的话说,“程序测试可以用来显示错误的存在,但永远不会显示它们的缺失!59 或者,正如阿尔伯特·爱因斯坦所说,“再多的实验也无法证明我是对的。一个实验就能证明我错了。

为什么会这样?即使是最简单的程序也有数十亿个可能的输入。例如,考虑一个声称满足规范的程序

image.png

至少可以说,在所有整数对上运行它将是乏味的。我们能做的最好的事情就是在整数对上运行它,如果程序中存在错误,这些整数对有合理的概率产生错误的答案。

测试的关键是找到一个称为测试套件的输入集合,该集合很有可能发现错误,但运行时间不会太长。执行此操作的关键是将所有可能输入的空间划分为子集,这些子集提供有关程序正确性的等效信息,然后构造一个测试套件,其中包含每个分区的至少一个输入。(通常,构建这样的测试套件实际上是不可能的。认为这是一个无法实现的理想。

集合的分区将该集合划分为子集的集合,以便原始集的每个元素恰好属于其中一个子集。例如,考虑is_smaller(x, y)。集合可能的输入是整数的所有成对组合。将此集合分区的一种方法是分成以下九个子集:

image.png

如果我们在每个子集中的至少一个值上测试实现,我们将有很好的机会(但不能保证)暴露一个错误(如果存在)。

对于大多数程序来说,找到一个好的输入分区说起来容易做起来难。通常,人们依赖于基于通过代码和规范的某种组合来探索不同路径的启发式方法。基于探索代码路径的启发式方法属于一个称为玻璃盒(或白盒)测试的类。基于探索规范路径的启发式方法属于一个称为黑盒测试的类。