Python的錯誤除錯以及單元測試unittest
錯誤處理:
程式執行的過程中如果發生了錯誤,就可以事先約定返回一個錯誤程式碼,但是用錯誤碼錶示是否出錯十分的不方便,所以高階語言通常都內建了一套try...except...finally
的錯誤處理機制,Python也有這樣的錯誤處理機制:
try: print('try...') r = 10 / 0 print('result:', r) except ZeroDivisionError as e: # try執行過程中如果哪一行出錯,就不會執行後續程式碼,直接跳轉到這個程式碼塊 print('except:', e) except ZeroDivisionError as e: # 可以有多個except來捕獲不同的錯誤 print('ZeroDivisionError:', e) else: #當沒有錯誤發生的時候,會執行else語句塊 print('no error!') finally: # 如果有finally語句塊,就不管對錯一定會執行這段程式碼 print('finally...') print('END')
Python的錯誤是class,所有的錯誤都繼承自BaseException
,所以每次捕獲一個型別的錯誤,也會捕獲這個型別錯誤的所有子類:
點選這裡看錯誤型別和繼承關係
記錄錯誤使用logging
模組:
既然我們能捕獲錯誤,那麼我們就可以把錯誤堆疊記錄下來,事後分析錯誤原因,同時讓程式繼續執行下去,Python內建的logging模組就可以記錄錯誤資訊。
import logging def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): try: bar('0') except Exception as e: # 程式列印完錯誤會繼續執行並正常退出 logging.exception(e) main() print('END')
logging可以通過配置把錯誤記錄到日誌檔案裡面
丟擲錯誤:
捕獲錯誤就是捕獲到一個錯誤class的一個例項,所以錯誤都是有意建立並丟擲的,所以我們也可以使用自己編寫的函式丟擲錯誤:
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
這裡注意儘量使用Python的內建錯誤型別
除錯:
第一種方法:print
除錯的最簡單辦法就是用print()
第二種方法:斷言
def foo(s):
n = int(s)
assert n != 0, 'n is zero!' # 這裡斷言的意思是n應該不等於0,否則就丟擲AssertionError的錯誤,並列印後面的‘n is zero'
return 10 / n
def main():
foo('0')
這樣輸出結果還是會有很多的垃圾資訊,在啟動Python直譯器的時候可以用-0
來關閉assert:python -O err.py
這樣關閉後,可以把所有的assert語句看成是pass
第三種方法:logging
logging不會輸出錯誤,而且可以輸出到檔案:
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
logging不會丟擲錯誤,可以輸出到檔案,還可以允許指定記錄資訊的級別,有debug, info, warning, error等幾個級別,最後統一控制輸出哪個級別的資訊。
第四種方法:pdb 單步執行
讓程式以單步執行的方式執行,可以設定斷點,可以用IDE設定斷點更為方便。
單元測試,unittest
單元測試用來對一個模組,函式或者類來進行正確性檢驗
打比方,有一個函式abs()
,我們可以有一個測試用例:輸入正數如1, 0.5之類的,期待返回值與輸入值相同。
例子: mydict.py
class Dict(dict):
def __init__(self, **kw):
super().__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
然後我們編寫單元測試:mydict_test.py
import unittest
from mydict import Dict
class TestDict(unittest.TestCase): # 編寫一個測試類,從unittest.TestCase繼承
def test_init(self): # 以test開頭的就是測試方法,不然就不是且不會被執行
d = Dict(a=1, b='test')
self.assertEqual(d.a, 1)
self.assertEqual(d.b, 'test')
self.assertTrue(isinstance(d, dict))
def test_key(self):
d = Dict()
d['key'] = 'value'
self.assertEqual(d.key, 'value') # 這是最常用的斷言,希望輸出與我們預期值相等
def test_attr(self):
d = Dict()
d.key = 'value'
self.assertTrue('key' in d)
self.assertEqual(d['key'], 'value')
def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError): # 另一種重要的斷言就是期待丟擲指定型別的error
value = d['empty'] # 這裡通過訪問不存在的key時,斷言會丟擲KeyError
def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty
這裡最常用的斷言就是assertEqual()
:
self.assertEqual(abs(-1), 1)
執行單元測試:
推薦使用在命令列通過引數-m unittest
直接執行單元測試:
$ python -m unittest mydict_test
setUp & tearDown:
可以在單元測試中編寫兩個特殊的方法:setUp()
和 tearDown
方法,這兩個方法會在每呼叫一個測試方法的前後分別被執行,所以前者可以用來連線資料庫,後者用來關閉資料庫。