MIT 6.00 1X Lecture 7 Debugging 学习笔记

l MIT6.00 1x (麻省理工:计算机科学和Python编程导论)

Lecture 7 Debugging 调试

7.1 TESTING AND DEBUGGING 测试和调试

Testing and Debugging 测试和调试

• Would be great if our code always workedproperly the first $me we run it!  如果我们的代码总能在第一次运行就完美运行,那就太棒了

• But life ain’t perfect, so we need:  但是人生不完美的,所以我们需要

– Testing methods  测试方法

• Ways of trying code on examples to determine if running correctly  使用不同的例子测试代码并查看他是否正确

– Debugging methods  调试方法

• Ways of fixing a program that you know does not work as intended 修改不正常程序的方法

When should you test and debug? 应该在什么时候测试和调试

• Design your code for ease of testing anddebugging  提前弄清楚我们的代码将使我们测试和调试变得简单

– Break program into components that can be tested and debuggedindependently   它们将代码分解成独立的模块,从而独立的进行测试和调试

– Document constraints on modules  写出好的文档

• Expectations on inputs, on outputs  我对输入和输出的期望是什么

• Even if code does not enforce constraints, valuable for debuggingto have description  即使通过测试,代码没有超过限制,这对我们调试的描述也是有用的,特别是当你准备追查一段代码为啥没有按照预期执行时

 

When are you ready to test? 什么时候准备好了测试

• Ensure that code will actually run  保证代码可以正常运行

– Remove syntax errors  剔除语法错误

– Remove static semantic errors  删除静态语义错误

– Both of these are typically handled by Python interpreter  python程序会发现这些东西

• Have a set of expected results (i.e.input-output pairings) ready  提前摸索出一套预期的结果对于一个特定的输入,我们会期待程序有怎样的输出

7.2 TEST SUITES 测试套件

Testing 测试

• Goal:

– Show that bugs exist   证明错误却是存在

– Would be great to prove code is bug free, but generally hard   如果能证明代码没问题,那非常棒,但是那是非常难以做到的没有错误的

• Usually can’t run on all possible inputs to check  一个原因是我们无法做到所有的测试

• Formal methods sometimes help, but usually only on simpler code 有一些正式的方法,它们基于数学,有时候会帮我们证明代码没有错误,但是通常只对简单代码有效

Test suite 测试套件

• Want to find a collection of inputs thathas high likelihood of revealing bugs, yet is efficient   找到一系列输入,它们很有可能会暴露错误,这样的测试实际上非常有效,这就是所谓的测试套件

– Partition space of inputs into subsets that provide equivalentinformation about correctness   其思想史我们将输入分解成子集,为代码的正确性提供等效信息

• Partition divides a set into group of subsets such that eachelement of set is in exactly one subset    我们要做的就是将所有可能的输入集分割成一系列的子集,并保证每一个元素都现在一个子集里

– Construct test suite that contains one input from each element ofpartition   然后我将构建一个测试集,其中至少包含每个子集的一个元素

– Run test suite 运行测试集,并检查它能否成功运行

Example of partition 分解成子集的例子

def isBigger(x, y):

 “““Assumes x and y are ints returns True if xis less than y else False”””

假定x和y是整数,如果x比y小返回真否则返回假

• Input space is all pairs of integers   输入空间包含所有成对的指数

• Possible partition  可能的分区

– x positive, y positive  xy都是正数

– x negative, y negative xy都是负数

 – x positive, y negative  x正y负

– x negative, y positive  x负y正

– x = 0, y = 0  xy都是0

– x = 0, y != 0  x是0 y不是0

– x != 0, y = 0  x不是0 y是0

Why this partition? 为什么要这样分区

• Lots of other choices  我们有很多其他的选择

– E.g., x prime, y not; y prime, x not; both prime; both not   例如,x是质数而y不是,y是质数而x不是,xy都是质数,xy都不是质数(但是这与问题不相关)

• Space of inputs often have naturalboundaries  一些输入空间经常有些自然界限

– Integers are positive, negative or zero   整数 要么是正数 要么是负数 要么是0

– From this perspective, have 9 subsets   但是从这个方面考虑,应该有九种可能性啊

• Split x = 0, y != 0 into x = 0, y positive and x =0, y negative  但是例如我们数的下是0 y不是0就包含了两种,x是0 y是正数和x是0 y是负数的可能性

• Same for x != 0, y = 0 还有 x不是0 y是0也包含两种可能性

Partitioning 分区

• What if no natural partition to inputspace?   输入空间没有自然分区怎么办

– Random testing – probability that code is correct increases withnumber of trials; but should be able to use code to do better  我可以做随机测试,用一大堆例子来不断尝试,试验次数越多,代码的正确率越高,但是这种情况我们可以使用代码来达到更好的效果

两类测试

– Use heuristics based on exploring paths through the specifications– black-box testing  一是通过特定分类使用启发式的方法探索路径,我们称之为黑盒测试

– Use heuristics based on exploring paths through the code –glass-box testing   二是通过代码本身使用启发式的方式探索路径,我们称之为白盒测试

7.3 BLACK-BOX TESTING 黑盒测试

Black-box testing 黑盒测试

• Test suite designed without looking atcode  这个测试套件的设计使人们无需查看代码

– Can be done by someone other than implementer   他的优点在于制作代码以外的人可以用它来测试代码

– Will avoid inherent biases of implementer, exposing poten.al bugsmore easily  让其他人来进行测试可以避开制作代码的人的固有偏见

– Testing designed without knowledge of implementation, thus can bereused even if implementation changed 另一个好处是,测试不需要知道编写代码的知识,因此我们可以重复使用测试套件即使代码改变了

Paths through a specification 通过规范的路径

• Paths through specification:  规范的路径有两种情况

– x = 0

– x > 0

• But clearly not enough  清晰,但还不够

• Also good to consider boundary cases  还要考虑边界情况

– For lists: empty list, singleton list, many element list   对于列表:空列表,单例列表,多元素列表

– For numbers, very small, very large, “typical” 对于数字来说 数字很小或者数字很大还有其他非常“典型”的情况(对于不同的情况,典型的情况是不同的): 

Example 例子

• For our sqrt case, try these:  对于上面的平方根例子,试试这些输入值(图在下面)

– First four are typical   前四个是典型的

• Perfect square  完美的平方数

• Irrational square root   没有整数平方根的数

• Example less than 1   小于1的小数

– Last five test extremes   后五个输入值

• If bug, might be code, or might be spec (e.g. don’t try to findroot if eps tiny)    如果出现bug了,可能是由于代码的原因,也可能是由于规格(输入值)的原因(比如本例中,如果eps(精确度)太小了,可能会找不到平方根

7.4 GLASS-BOX TESTING 白盒测试

Glass-box Testing白盒测试

• Use code directly to guide design of testcases  使用代码本身来引导测试用例的设置

• Glass-box test suite is path-complete if every potential paththrough the code is tested at least once  一个好的白盒检验套件也被称为 穷举路径测试,其中代码片段每一条可能的路径都会被至少检测了一次

– Not always possible if loop can be exercised arbitrary times, orrecursion can be arbitrarily deep  这经常是不可能的比如一个循环被执行了任意次,或者递归被执行了任意深度

• Even path-complete suite can miss a bug, depending on choice ofexamples  特别注意即使是穷举路径测试,也可能会错过一个bug,这基于例子的选择

 

Example 例子

• Test suite of {-2, 2} will be pathcomplete  测试套件是{-2.2}的情况就可以包含所有测试路径 -2是if函数后的路径,+2是else函数后的路径

• But will miss abs(-1) which incorrectlyreturns -1    但是会错过一个bug -1的绝对值在本例中仍然是-1 而不是我们周知的+1 (边界条件)

– Testing boundary cases and typical cases would catch this {-2 -1, 2}   测试边界条件和典型条件 (-2,-1,2)

Rules of thumb for glass-box testing  白盒检验的基本规则

• Exercise both branches of all ifstatements   确保测试了if语句的所有分支

• Ensure each except clause is executed   确保每一个except语句都被测试了

• For each for loop, have tests where:     对于for循环语句,我不能执行所有的例子,但我可以执行三个案例

– Loop is not entered  没有进入循环

– Body of loop executed exactly once 循环主体只执行了一次

– Body of loop executed more than once   循环主体被执行了好多次

• For each while loop,    对while语句

– Same cases as for loops  和for循环一样的例子

– Cases that catch all ways to exit loop   额外的方面( 捕捉所有的跳出循环的不同路径

• For recursive functions, test with norecursive calls, one recursive call, and more than one recursive call  对于递归函数,我们可以测试如果没有递归会发生什么,如果递归了一次会发生什么,如果递归了不止一次会发生什么

7.5 TEST DRIVERS AND STUBS 测试驱动程序和存根

Conducting tests 进行测试

• Start with unit testing  单元测试的方法入手

– Check that each module (e.g. function) works correctly   单元测试会检测一个模块(比如函数)检测他是否正确  (算法错误)

• Move to integration testing  接着进行集成测试

– Check that system as whole works correctly   检验作为整体的系统是否正常工作    (集成错误)

• Cycle between these phases 修改完成后再倒回去进行单元测试和集成测试,直到找到所有的bug

Test Drivers and Stubs 测试驱动程序和存根

• Drivers are code that   驱动就是代码

– Set up environment needed to run code    我们将建立环境,,也就是说我们将建立一段代码,从而绑定全局变量、数据结构以及其他需要我做测试的东西

– Invoke code on predefined sequence of inputs   在每一种检测手段都运行一遍这段代码(输入序列)

– Save results, and 保存结果

– Report   输出报告

• Drivers simulate parts of program thatuse unit being tested   驱动的好处是他基本会模拟正在进行测试的单元

• Stubs simulate parts of program used byunit being tested   存根仅仅模仿了程序的一部分,这部分被正在进行测试的单元所使用

– Allow you to test units that depend on softwarenot yet written  存根的好处在于它可以测试一些依赖于软件的单元,即便你未曾写过他,可以做到这点

Good testing practice 好的测试实践是什么样子的

• Start with unit testing   从单元测试开始

• Move to integration testing   然后进行集成测试

• After code is corrected, be sure to do regression testing:  当我完成调试并且纠正完所有的错误的时候,记得做回归测试

– Check thatprogram still passes all the tests it used to pass, i.e., that your code fixhasn’t broken something that used to work 返回去,测试一下程序是否仍然能通过所有它之间已经通过的检验,即使之前的代码片段已经通过检验。

因为我已经进行了修改,我不确定是否改乱了之前的代码

7.6 DEBUGGING 调试

Debugging 调试

• The “history” of debugging  调试的历史

– Often claimed that first bug was found by team at Harvard that wasworking on the Mark II Aiken Relay Calculator  第一个bug是被哈佛的一个小组发现的,它们的项目后来被称为马克II型艾肯中继器计算机

– A set of tests on a module had failed; when staff inspected theactually machinery (in this case vacuum tubes and relays), they discoveredthis: 在这个计算机的里的模块测试总是失败,当时的工作人员爬进了电脑里,找到了bug的原因,是一只飞蛾(飞蛾英文bug)

A real bug! 现在我们所理解的bug

• However, the term bug dates back evenearlier:  然后bug这个词出现的可能更早

– Hawkin’s New Catechism of Electricity, 1896  1896年的一本教科书中说

• “The term ‘bug’ is used to a limited extent to designate any faultor trouble in the connections or working of electrical apparatus.”  “bug”这个词从某种程度上是指电子器械链接或者工作过程中出现的任何错误或麻烦

 

 

下面先讲一下bug的类型

Runtime bugs 运行中的bug分类

Overtvs. covert:   显性错误和隐形错误

Overt has an obviousmanifestation – code crashes or runs forever  显性意味着一个很明显的变化,代码崩溃了或者代码一直运行

Covert has no obviousmanifestation – code returns a value, which may be incorrect but hard todetermine    隐形错误更加不易察觉,它没有明显的变化,代码返回一个值,但是这个值不好判断他正不正确,

•Persistent vs. intermittent:    永久性错误和间歇性错误

Persistent occurs everytime code is run  永久性错误在代码每次运行时都会发生

Intermittent onlyoccurs some times, even if run on same input  间歇性错误仅仅在有些时候发生,有时候它仅仅在特定输入时发生,有时候输入相同还是会偶尔发生错误

Categories of bugs  bug的分类

• Overt and persistent   显性永久代码

– Obvious to detect    很容易发现

– Good programmers use defensiveprogramming to try to ensure that if error is made, bug will fall into thiscategory   好的程序员会使用防御性编程,来确保如果错误发生了,错误将会落入到一个显而易见的范畴里

• Overt and intermittent    显性间歇性代码

– More frustrating, can be harder to debug, but if conditions thatprompt bug can be reproduced, can be handled   很不幸,更难通过调试来解决,但是如果可以重现出现错误的情况,我们也就可以试着解决它

• Covert  隐形错误

– Highly dangerous, as users may not realize answers are incorrectuntil code has been run for long period  隐形错误非常危险,不明显不易察觉,它可能是间歇性错误,那就更麻烦了!

7.7 DEBUGGING AS SEARCH 调试搜索

Debugging skills 调试技巧

• Treat as a search problem: looking forexplanation for incorrect behavior   将调试看作搜索问题:我想找到一种解释,解释代码中为什么会出错

– Study available data – both correct test cases and incorrect ones   研究手头的数据,正确的测试用例和错误的测试用例都要看

– Form an hypothesis consistent with the data   建立一个和数据一致的假设

– Design and run a repeatable experiment with potential to refutethe hypothesis   设计和运行一个可重复的试验,但会包含驳回假设的可能性

– Keep record of experiments performed: use narrow range ofhypotheses  保持在试验中纪录,使用范围更小的假设

Debugging as search 把调试堪称搜索

• Want to narrow down space of possiblesources of error  选出可能出现错误的范围,然后把他不断缩小

• Design experiments that exposeintermediate stages of computation (use print statements!), and use results tofurther narrow search  设计一些东西,帮我们暴露代码计算的中间环节(通过使用print语句(将我期望的信息和实际输出的信息对比)),然后使用结果缩小搜索范围

• Binary search can be a powerful tool forthis  二分法对于调试搜索来说是个好办法

 

这个函数的功能是想问一个字符串是不是回文结构

 

Ps  assert是断言函数,我们将在下一章详细学习他

Stepping through the tests 步进试验

• Suppose we run this code:  假设我们开始运行这段代码了(silly)

– We try the input ‘abcba’, which succeeds  我们运行abcba 正确!

– We try the input ‘palinnilap’, which succeeds   我们输入 palinnilap 正确

– But we try the input ‘ab’, which also ‘succeeds’  我们输入ab 他却不正确了

• Let’s use binary search to isolate bug(s)  我们可以用二分法分离出错误的具体位置

• Pick a spot about halfway through code,and devise experiment   在代码的中间的位置,并设计一个试验

– Pick a spot where easy to examine intermediate values 选择这点,因为很容易检测他的中间值,在这里放置一个print语句

在代码中间(循环之后)加了一个print(result)语句

 

• At this point in the code, we expect (forour test case of ‘ab’), that result should be a list [‘a’, ‘b’]  这个时候代码中我们期待的结果是输出一个列表[”a”,”b”](我们输入的是ab)

• We run the code, and get [‘b’].  我们运行程序,却只得到了[“b”]

• Because of binary search, we know that atleast one bug must be present earlier in the code   由于二分搜索,我们知道了至少一个bug在print语句之前的代码中

• So we add a second print 所以我们添加第二个print语句

 

在循环中添加一个print语句

Stepping through一步一步通过

• When we run with our example, the printstatement returns   当我们运行这个例子的时候,输出语句会返回

– [‘a’]   第一次输入a,返回a

– [‘b’]   第二次输入b  返回b (应该返回a,b的列表)

• This suggests that result is not keepingall elements  这个现象说明,result没有保留有所有的元素  经过观察我们发现 result在每次循环都会重新定义

– So let’s move the initialization of result outside the loop andretry  所以我们将结果的初始化移动到循环之外,然后重试

现在我们第二次输入b后显示的是正确的值(值是[a,b])了

但是程序依然返回了yes,说明ab结构仍然是回文结构,说明这段代码仍然有问题

 

• So this now shows we are getting the datastructure result properly set up, but we still have a bug somewhere   它表示数据结构已经构建完毕,但是我们仍然有一个bug在一些地方

– A reminder that there may be more than one problem!   这是一个很好的提醒,意味着仅仅修正了一个代码的问题,其他地方就没有错误了

– This suggests second bug must lie below print statement; let’slook at isPal   这其实也暗示了第二个代码一定在输出语句的下面,那其实应该是ispal的定义有问题

– Pick a point in middle of code, and add print statement again  重复上述过程,选择一个代码的中点,把输出语句加入进去

 

 

 

 

我们把输出语句 print(temp,x)加入到了代码中,这个输出语句可以表示出两个变量,而且这两个变量应该是相反的。

 

但是事实上并不是这样的,x和temp这两个变量是完全一样的

• At this point in the code, we expect (forour example of ‘ab’) that x should be [‘a’, ‘b’], but temp should be [‘b’,‘a’], however they both have the value [‘a’, ‘b’]    在这个地方,x应该是[“a”,”b”],temp应该是[“b”,”a”],但是他们的值却都是[“a”,”b”]

• So let’s add another print statement,earlier in the code  所以让我们继续添加print语句,在代码更往前的地方

 

 

 

在temp = x之后,temp.reverse之前

添加了一个print(temp,x)语句,继续检测变量的值

我们发现两个print语句输出的值是一样的,temp和x的值也都是一样的

 

• And we see that temp has the same valuebefore and after the call to reverse   我们可以看到temp和x的值是一样上,无论reverse之前还是之后

• If we look at our code, we realize wehave committed a standard bug – we forgot to actually invoke the reverse method  让我们看一看代码,我们就发现了代码,我们用错了代码,reverse语法应该是这样的,   列表名.reverse()

– Need temp.reverse()  语句需要改成这样temp.reverse()

• So let’s make that change and try again  把这个错误改正过来然后重试一下

 

 

 

 

现在我们改正了错误再来运行一下代码,看看有没有问题

我们又发现了问题,第二次print语句输出的temp值和x的值都反向了,这不对了啊

 

 

• But now when we run on our simpleexample, both x and temp have been reversed!!   但是现在我们运行我们的例子的时候,x和temp都反向了啊!!!

• We have also narrowed down this bug to asingle line. The error must be in the reverse step    我们可以确定错误一定处在两个print语句中夹着那句反向语句中,因为它们之前的语句正确,之后的语句却错误

• In fact, we have an aliasing bug –reversing temp has also caused x to be reversed   事实上,我们可能遇到了别名错误,,反转temp变量,也把x变量反转了

– Because they are referring to the same object  因为他们指向了同样的变量

(当我对某物进行反向时,我创建的另一个名称却指向了同样的东西,这就导致了另一个反向)

 

 

我们修复一下它(用的是列表的克隆的方法),即不让temp指向x的值,而让temp指向x的副本,把temp=x这个语句换位 temp=x[:]

然后我们再来运行一下他

 

这次的输出结果是 第一次 [“a”,”b”],[“a”,”b”] 

第二次[“a”,”b”] ,[“b”,”a”]

正确啦~~

• And now running this shows that beforethe reverse step, the two variables have the same form, but afterwards onlytemp is reversed.  这次的运行显示,反向之前两个变量有相同的值,反向之后却是不同的值了

• We can now go back and check that ourother tests cases still work correctly 我们要返回去,用其他的值检测一下他是不是依然正确

Some pragmatic hints 一些实用的提示

• Look for the usual suspects  查看最长犯错误的地方,(典型错误)(忘了边缘条件,传入的参数是错误的,反向参数的顺序,忘记了调用函数的方法,到底是调用还是仅仅访问了一下)

• Ask why the code is doing what it is, notwhy it is not doing what you want  不要问代码为什么没有做你想做的事,而是问代码正在做什么事(尝试关注代码正在做什么,而不是尝试找出错误发生的地方)

• The bug is probably not where you thinkit is – eliminate locations   bug可能不在你想象的位置(排除错误可能出现的位置,通常是用二分法)

• Explain the problem to someone else  试一试跟别人解释这个问题,(对问题的说明常常会帮助你发现错误可能的位置

• Don’t believe the documentation  不要相信说明文档! (这是一类很糟糕的文档,它很容易延迟你的调试时间,而且有时说明文档也是错误的)

• Take a break and come back to the buglater  如果有困难的话,可以休息一下出去散散心,再回来解决它

 

 

总结

这一节我们学习了测试和调试,测试分为黑盒测试和白盒测试,

黑盒测试我们通过特定规范的执行路径,而白盒测试我们通过代码内部的结构来确定我们的输出序列

我们还讨论了调试的相关知识,也就是说,定位出我们代码错误的位置

特殊之处在于,我们将会调试做为一个搜索,并使用二分搜索的方法,并检测错误的来源

你可能感兴趣的:(MIT6.00,1X,python)