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(精確度)太小了,可能會找不到平方根
? 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是斷言函數,我們將在下一章詳細學習他
? 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 如果有困難的話,可以休息一下出去散散心,再回來解決它
總結這一節我們學習了測試和調試,測試分為黑盒測試和白盒測試,
黑盒測試我們通過特定規范的執行路徑,而白盒測試我們通過代碼內部的結構來確定我們的輸出序列
我們還討論了調試的相關知識,也就是說,定位出我們代碼錯誤的位置
特殊之處在于,我們將會調試做為一個搜索,并使用二分搜索的方法,并檢測錯誤的來源
就愛閱讀www.92to.com網友整理上傳,為您提供最全的知識大全,期待您的分享,轉載請注明出處。歡迎轉載:http://www.kanwencang.com/bangong/20161206/63510.html
文章列表