12. 除錯、測試與效能
• 學習目標
– 使用 pdb 模組除錯
– 對程式進行單元測試
– 使用 timeit 評測程式片段
– 使用 cProfile(profile)
察看評測數據
2
除錯
• 檢測錯誤的時候,有個順手的工具可以加
速錯誤的檢出,其中 Debugger 是最常使
用也是最基本的工具之一
– 中斷點
– 檢視變數
– 逐步執行
3
4
5
6
• Step Over 就是執行程式碼的下一步,如
果下步是個函式,會執行完該函式至返回
• 使用 Step Into,若下一步是個函式呼叫,
就會進入函式逐步執行,以便查看函式中
的演算與每一步執行結果
• 如果目前正在某個函式之中,接下來不想
逐步檢視函式中剩餘之程式碼,可以執行
Step Out
7
使用 pdb 模組
• 如果手邊正好沒有整合開發環境,只能在
文字模式下執行程式進行除錯,那麼可以
使用 Python 內建的 pdb 模組
• 直接使用 python -m pdb,指定想要除
錯的 .py 檔案
8
9
10
11
12
• 如果想要單點針對某個函式進行除錯,可
以使用 pdb.run()函式,通常可以在
REPL 中進行這類動作
13
14
• 可以將 pdb.set_trace() 直接撰寫在原
始碼中,當程式執行到 pdb.set_trace()
時,就會進入 (Pdb) 指令提示
• 若程式因為例外而無法繼續下去的話,可
以再使用 pdb.pm() 回到例外發生時的上
一步
15
16
17
測試
• Python 變數沒有型態,如果有型態上的操
作錯誤,基本上會是在執行時期運行至該
段程式碼時,才會產生錯誤訊息
• 檢查出型態不正確的任務,必須由開發者
來承擔,而減輕這個負擔的最好方式之一,
就是撰寫良好的測試程式
18
• 撰寫測試的相關工具
– assert
– doctest
– unittest
– 第三方測試工具(像是 nose、pytest 等)
19
• 要在程式中安插斷言,可以使用 assert
• 使用 assert expression 的話,相當於
以下的程式片段:
• 如果有兩個 expression,例如 assert
expression1, expression2:
20
• __debug__ 是個內建變數,一般情況下會
是 True
• 如果執行時需要最佳化時(在執行時加上-
O 引數)則會是 False
21
22
• 何時該使用斷言呢?
– 前置條件斷言客戶端呼叫函式前,已經準備好
某些條件。
– 後置條件驗證客戶端呼叫函式後,具有函式承
諾之結果。
– 類別不變量(Class invariant)驗證物件某個
時間點下的狀態。
– 內部不變量(Internal invariant)使用斷言取
代註解。
– 流程不變量(Control-flow invariant)斷言程
式流程中絕不會執行到的程式碼部份。
23
24
25
26
撰寫 doctest
• doctest 模組一方面是測試程式碼,一方
面也用來確認 DocStrings 的內容沒有過期
• 使用互動式的範例來執行驗證,開發者只
要為套件撰寫 REPL 形式的文件就可以
27
28
29
• 獨立地撰寫在另一個文字檔案中
30
31
使用 unittest 單元測試
• unittest 模組有時亦稱為"PyUnit",是
JUnit 的Python 語言實現
• JUnit 是個 Java 實現的單元測試(Unit
test)框架
• 單元測試指的是測試一個工作單元(a unit
of work)的行為
32
• 測試一個單元,基本上要與其他的單元獨
立,否則會是在同時測試兩個單元的正確
性,或是兩個單元之間的合作行為
• 就軟體測試而言,單元測試通常指的是測
試某個函式(或方法)
33
• unittest 模組主要包括四個部份:
– 測試案例(Test case)
– 測試設備(Test fixture)
– 測試套件(Test suite)
– 測試執行器(Test runner)
34
測試案例
35
36
測試設備
• 如果有定義 setUp() 方法,那麼執行每個
test 開頭的方法前,都會呼叫一次
setUp()
• 如果有定義 tearDown()方法,那麼執行
每個 test 開頭的方法後,都會呼叫一次
tearDown()
• 可以使用 setUp()、tearDown()來分別
定義每次單元測試前後的資源建立與銷毀
37
測試套件
• 想要將不同的測試組合在一起
• 想要自動載入某個 TestCase 子類別中所
有 test_xxx 方法
38
• 將某個測試套件與某個 TestCase 中的
test_xxx 方法組合為另一個測試套件:
• 將許多測試套件再全部組合為另一個測試
套件:
39
測試執行器
• 在程式碼中直接使用 TextTestRunner:
• 在命令列中使用 unittest 模組來運行模
組、類別或甚至個別的測試方法:
40
timeit 模組
• 用來量測一個小程式片段的執行時間
41
• 到底是使用 + 時比較快,還是 join() 比
較快呢?
42
43
• 效能是整體程式結合之後的執行考量,並
不是單一元素快慢的問題,也不是憑空猜
測,而是要有實際的評測作為依據
• timeit 預設是執行程式片段 1,000,000 次,
然後取平均時間
• 執行次數可透過 number 參數控制
44
• 以下是幾個直接透過 API 運行的範例:
45
46
使用 cProfile(profile)
• cProfile 用來收集程式執行時的一些時
間數據,提供各種統計數據,對大多數的
使用者來說是不錯的工具
• 這是用 C 撰寫的擴充模組,在評測時有較
低的額外成本,不過並不是所有系統上都
有提供
• profile 介面上仿造了cProfile,是用
純Python 來實現的模組,因此有較高的互
通性
47
48
• ncalls:"number of calls"的縮寫,也就是對
特定函式的呼叫次數。
• tottime:"total time"的縮寫,花費在函式上
的執行時間(不包括子函數
• 呼叫的時間)。
• percall:tottime 除以ncalls 的結果。
• cumtime:"cumulative time"的縮寫,花費
在函式與所有子函式的時間
• (從呼叫至離開)。
• percall:cumtime 除以ncalls 的結果。
• filename:lineno(function):提供程式碼執行
時的位置資訊。 49
• 可以使用 pstats 對 cProfile 的結果,
進行各種運算與排序
50

12. 除錯、測試與效能