(笔记)计算机科学和PYTHON编程导论 第七讲——调试

课程内容及目标:

  • 讨论测试和调试。讨论如何测试代码,以确定是否可能有bug
  • 黑盒测试
  • 白盒测试
  • 讨论调试的相关技术

1.我们应该在什么时候进行测试?什么时候调试bug

如果我们提前弄清楚如何设计我们的代码,那么测试和调试将会更加简单。

优秀的程序员会如何设计:

(1)将代码分解成独立的模块,从而独立地进行测试和调试

(2)写出好的文档。在进入代码前,认真想想并写下你下一步将干什么,我对输入的期望是什么?我对输出的期望是什么?

(3)会记录下可能的假设


一旦我们设计好了代码,就可以进行测试了。为了测试它,基本上我们需要做两件事:

(1)剔除任何的语法错误

(2)删除任何静态语义错误


2.测试的方法:

目标:

(1)确认错误确实存在

(2)如果能证明bug不存在是最好的,但事实上,难以做到。比起证明一件事情不存在,证明一件事情存在要容易得多。


我们没有时间去将所有的可能性都测试一遍,所以需要一种方法来完成测试。我们来分析一下:

我们想做的是找到一系列输入,它们很有可能暴露错误,但这样的测试实际上非常有效,这就是所谓的测试套件,其思想就是我们将进行分解,将输入分解成子集,为代码正确性提供等效信息。所以我们要做的是将所有可能的输入集分割成一系列的子集,并且保证每个元素都在一个子集里。一旦分解完成,将会构建一个测试集,其中至少包含每个子集中的一个元素。从每个子集中,挑选一个输入元素,然后运行测试,看是否能够成功。举个栗子:

def isBigger(x, y):
    '''
    功能:判断x是否大于y,假设x和y都是整型
    如果x<y将返回真,否则为假
    '''

输入空间:所有成对的整数

可能的分割:

——x为正,y为正

——x为负,y为负

——x为正,y为负

——x为负,y为正

——x=0,y=0

——x=0,y≠0

——x≠0,y=0


这样分解的原因:

其实还有很多分割的方法,如x是个质数而y不是质数等等,但是这种分割方法跟问题不相关。这个输入空间的分割其实被称为自然分区,即看到整数,那它自然有正数、负数和0三种分区。当然我们也可以分割成九种情况。毕竟x和y分别有正数、负数和0三种情况。


构成测试套件:

(1)通过特定分类使用启发式的方法探索路径,称之为黑盒测试:

优点:可以避免发生偏见;测试不需要实施的知识。

这里有段代码:

def sqrt(x, eps):
    """Assumes x,eps floats
        x>=0
        eps>0
      return res such that
        x-eps<=res*res<=x+eps"""

规格路径:

——x=0

——x>0

按理来说需要两种测试用例即可。但是这是不够的。有了经典测试还应该有边界测试,如下面的几个测试用例可以很好地体现这一点:

 x eps
0.00.0001
25.00.0001
.050.0001
2.00.0001
2.01.0/2.0**64.0
1.0/2.0**641.0/2.0**64.0
2.0**64.01.0/2.0**64.0
1.0/2.0**64.02.0**64.0
2.0**64.02.0**64.0
前四个测试为经典测试,注意到eps非常小,精确度很高,非常接近答案。然后从规格中选择了0这个示例,然后是一些正值的示例。还挑选了一个完美的可被平方根的25.0,还有根不是很合理的平方数。接下里,就是边界测试了,第五行x仍旧选择了和经典测试中相同的2.0,然而eps被赋予了特别小的值。接下来的几行,例子中的x要么非常大,要么非常小,与此同时,eps要么非常大,要么非常小

(2)基于代码本身使用启发式的方法探索路径,称之为白盒测试:

这个方法要利用代码本身进行检测。一个好的白盒检测套件,也被称作穷举路径测试,其中代码片段的每一条可能路径至少都被检测了一次,但几乎不可能。如果有一个循环执行了10亿次,我们可能就不能够完成这个任务了。我们可以提供几个不同的检测方案,这和递归有异曲同工之妙。举个例子:

def abs(x):
    """Assumes x is an int
       returns x if x>=0 and -x otherwise"""
    if x<-1:
        return -x
    else:
        return x

这段代码用于寻找绝对值。那么测试套件的选择:

{-2,2},这是个穷举测试路径,既检测了if,还检测了else

但是漏了-1这种情况,当x为-1时应该执行if语句而不是执行else语句。即我们依旧要检验边界情况,即:

{-2,-1,2}

这里有几条要遵守的规则:

<1>确保检查了if语句的所有分支

<2>确保都测试了每次例外

<3>如果我有一个循环,一个for循环,要检测循环之前的程序,即循环体刚好被执行了一次,或者该循环体本应执行多次,如前面所述,不可能执行所有检测,那么我们可以抽取三个案例,因为它们很可能暴露错误。

<4>对while循环也是如此

<5>如果有个递归函数,那么需要测试一下没有递归调用会发生什么。如果刚好有一个递归调用,又会发生什么。同样,有多次递归又会发生什么。即0次、1次和多次的测试。


3.开始测试:

将从名为单元测试(unit testing)的方法入手:

单元测试会选择一个模块,比如一个函数,然后检测它。用输入值检测它,这些结果应该是我们可以预期的。这就会抓住一些算法错误。

完成单元测试后,下一步就是进行集成测试(integration testing),检验作为整体的系统是否正常工作。如果有若干个互相作用的函数,这将会抓住一些所谓的交互错误。

循环以上两个步骤,直到抓到了所有可能的错误。

还有一个测试很有用,就是建立一个测试驱动。驱动就是代码,建立它们就是为了进行测试。首先将会创建一段代码,从而绑定全局变量、数据结构以及其他我需要做测试的东西,之后拿着这套检测套件——即输入序列——在每一种检测手段中都运行一遍这段代码,保存结果,并且输出报告

注意,在循环测试完毕后,还要进行回归测试,即将之前用于测试的套件在新的代码汇总进行测试。


4.调试(Debugging):

错误分为显性错误和隐性错误,永久性错误和间歇性错误。

如何调试:

①研究手头的数据。正确的测试用例和错误的测试用例都要看。

②建立一个和数据一致的假设

③设计并运行一个可重复的实验,但会包含驳回假设的可能性。

④要一直记录游览一遍实验中所做的事,这会帮助我们缩小假设的范围,即代码中什么出错了。


然后将调试看做检索。我们要做的是选出可能出现错误的范围,然后把它不断缩小。设计一些东西,帮助我们暴露计算的中间环节。一个非常好并简单的工具就是打印语句。举个例子:

下面代码用于检测是否是回文结构:

def isPal(x):
    assert type(x) == list
    temp = x
    temp.reverse
    if type == x:
        return True
    else:
        return False

def silly(n):
    for i in range(n):
        result = []
        elem = raw_input('Enter element:')
        result.append(elem)
    if isPal(result):
        print('Yes')
    else:
        print('No')

进行测试发现,输入'ab'后依旧返回Yes,出现错误,那么使用二分查找来分离出错误的具体位置:

选择代码的中点并放置一个打印语句:


发现输入'ab'仅输出列表['b']。然后锁定错误区域在上半部分,在上半部分继续运用二分查找。发现错误改正后如下:


输入'ab'后发现输出列表为['a','b'],是对的,但返回结果仍为Yes,错误锁定在下半部分,即isPal函数。在isPal函数主要部分二分查找,插入print:


以检测temp和x是否互为回文,发现结果并不互为回文,输入'ab'后依旧为['a','b'] ['a','b']。那么问题锁定在上半部分。继续插入print:


反向之前temp和x是多少。发现依旧是['a','b']。也就是说,反向语句没有执行它的任务。修改为temp.reverse()即可。输入'ab'后,发现又出问题了,x也跟着temp反向了。意味着遇到了别名错误,这表示当对某物进行反向时,创建的另一个名称却也指向了同样的东西,这就导致另一个的反向。


这里我们不让temp指向x的值,而是指向x的副本。


5.一些提示:

①查看最常发生错误的地方,这些地方会发生典型错误

②如果你找不到错误的位置,不要问为什么代码没有做你想做的事情,而是问为什么代码在做一件它应该做的事情。关注代码在做什么,而不是尝试找出错误发生的地方。

③错误可能不在你想象的位置。或者说,排除那些错误可能出现的地方

④和别人交流

⑤不想相信说明文档

⑥确实被困住的话,就先暂时放下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值