1. 程式人生 > >Python的錯誤除錯以及單元測試unittest

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 方法,這兩個方法會在每呼叫一個測試方法的前後分別被執行,所以前者可以用來連線資料庫,後者用來關閉資料庫。