python中的 錯誤處理、調試和測試

錯誤處理

和Java類似,Python提供了一套錯誤處理機制,語法是 try...except...finally...

可以將你認爲會發生錯誤的代碼用try包裹起來並用except捕獲指定的錯誤或異常,最後使用finally執行語句塊如發生錯誤後也要進行資源的回收等。
Python的錯誤其實也是class,所有的錯誤類型都繼承自BaseException

出錯的時候,一定要分析錯誤的調用棧信息,才能定位錯誤的位置

可以通過python內置的logging模塊來記錄異常錯誤信息如logging.exception(e),這個logging模塊後面會有專門的文章來介紹。

我們也可以手動向上層拋出異常,可以通過raise關鍵字,如拋出屬性找不到的異常raise AttributeError("'Model' object has no attribute '%s'" % key)。在except代碼塊裏捕獲到異常後我們也可以通過raise將異常原封不動地拋出,也可以拋出自定義錯誤。

如下圖,我們可以看出AttributeError就是最終繼承於BaseException
在這裏插入圖片描述
如下面代碼所示。我在main()方法裏寫了多個except語句來捕捉各個異常,但是由於捕捉到異常後是從上到下匹配的且ExceptionZeroDivisionError的父類,故而except ZeroDivisionError as e:永遠也執行不了。
捕捉到異常後,我通過logging模塊來記錄異常錯誤信息並用raise將錯誤信息原樣拋出給上層。當然是否需要拋出看當時業務需求。

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:
        print('Error:', e)
        # 用logging模塊記錄異常
        logging.exception(e)
        raise  # 原樣將捕捉到的異常拋出
        # raise ZeroDivisionError("除數爲0發生的錯誤")  # 拋出指定異常
    except ZeroDivisionError as e:
        pass  # 注意這裏是永遠執行不到的,因爲Exception是ZeroDivisionError的父類,except捕捉到異常後從上往下匹配異常
    finally:
        print('finally...')

main()

運行結果如下圖所示
在這裏插入圖片描述

調試

print

最簡單粗暴的方式就是使用print()函數打印需要查看的變量的值。
但是這種方式僅限於平時學習使用。在工作當中是絕對不允許的。

斷言assert

這個和Java裏的斷言非常類似。都是用於判斷一個表達式,在表達式條件爲 false 的時候觸發異常。
語法是 assert expression [, arguments],等價於如下代碼。當然參數是可選的,不過一般最好添加參數,可以給人提示錯誤信息。

if not expression:
    raise AssertionError(arguments)

如下面計算一個數的平方,用assert判斷必須是數字,否則就會拋出異常

# 計算一個數的平方
def square(num):
    assert isinstance(num, (int,float)), "{} 不是數字類型".format(num)
    print("the square of {} is {}".format(num, num**2))

square(10)
square("10")

運行結果如下
在這裏插入圖片描述
當然如果程序中導出都充斥着assert也非常不好,一般斷言都是在測試代碼功能時發揮作用的。
不過我們可以在啓動python解釋器時通過參數-O來關閉assert,此時assert語句可以看做pass

如下所示。關閉assert後異常信息就不同了。注意參數是大寫的字母O不是數字零
在這裏插入圖片描述

logging

必須配置logging.basicConfig(level=logging.INFO)設定日誌級別,否則會信息是無法打印的。
更加詳細的配置信息後續再寫博客介紹。

import logging
logging.basicConfig(level=logging.INFO)
logging.info("測試中logging模塊")

在這裏插入圖片描述

pdb

啓動Python的調試器pdb(啓動python解釋器時添加參數-m pdb),讓程序以單步方式運行,可以隨時查看運行狀態。
我們也可以在代碼中導入pdb並在需要調試的地方寫上pdb.set_trace(),這樣就可以跳躍進行調試。

輸入 l 查看代碼。字母L的小寫。
輸入 n 執行下一行代碼。單步執行。
輸入 p 變量名 查看變量。
輸入 c 繼續運行到下一個設置pdb.set_trace()的代碼。
輸入 q 退出。

# -*- coding: UTF-8 -*-

import pdb
import logging
logging.basicConfig(level=logging.INFO)
logging.info("測試中logging模塊")


# 計算一個數的平方
def square(num):
    assert isinstance(num, (int,float)), "{} 不是數字類型".format(num)
    print("the square of {} is {}".format(num, num**2))
    return num**2

pdb.set_trace()
res = square(10)
print(1+2)
pdb.set_trace()
print(3+4)
logging.info("finish....")

運行結果如下
在這裏插入圖片描述

IDE

當然調試最好的方式還是IDE編輯器。如PyCharm等。
不過也最好結合logging去調試。

單元測試

爲了測試我們的代碼功能是否正常,可以寫一組測試用例放在一個模塊,這就叫做一個完整的單元測試。
當後續我們的代碼修改後,依舊可以運行這個單元測試保證我們的代碼在功能上是正常的。

python內置模塊unittest可以讓我們去寫單元測試。
首先我們需要編寫一個測試類,該測試類需要繼承unittest.TestCase。該測試類下以test開頭的方法就是測試方法,沒有以test開頭的方法就不是測試方法,在測試的時候不會被執行。

unittest.TestCase類提供了很多內置的條件判斷,最常用的就是assertEqualassertTrue以及發生異常時with self.assertRaises(AttributeError):

有兩種方式運行單元測試.

  • 在測試代碼裏添加unittest.main(),然後像運行普通python腳本一樣運行該單元測試文件。
  • 命令行輸入 python -m unittest 文件名 來執行單元測試。這種方式更加推薦常用,因爲這樣可以一次批量運行很多單元測試。

下面是我的mydict.py文件,自定義一個字典類

# -*- coding: UTF-8 -*-


class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError("'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

下面是我的單元測試文件mydict_test.py

# -*- coding: UTF-8 -*-


import unittest

from mydict import Dict


class TestDict(unittest.TestCase):

    def test_init(self):
        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):
            value = d['empty']

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empt

    def abc(self):
        10/0

    def test_abc(self):
        self.assertEqual(1+2, 3)

# 在用python -m unittest 文件名 執行單元測試時不需要下面兩行代碼
if __name__ == '__main__':
    unittest.main()

採用第一種方式運行單元測試結果如下圖所示
在這裏插入圖片描述
採用第二種方式運行單元測試結果如下圖所示
在這裏插入圖片描述

setUp與tearDown

可以在單元測試中編寫兩個特殊的setUp()tearDown()方法。這兩個方法會分別在每調用一個測試方法的前後分別被執行
設想你的測試需要啓動一個數據庫,這時,就可以在setUp()方法中連接數據庫,在tearDown()方法中關閉數據庫,這樣,不必在每個測試方法中重複相同的代碼。

class TestDict(unittest.TestCase):

    def setUp(self):
        print("setUp ... ")

    def tearDown(self):
        print("tearDown ... ")

在這裏插入圖片描述

單元測試小結

單元測試可以有效地測試某個程序模塊的行爲,是未來重構代碼的信心保證。

單元測試的測試用例要覆蓋常用的輸入組合、邊界條件和異常。

單元測試代碼要非常簡單,如果測試代碼太複雜,那麼測試代碼本身就可能有bug。

單元測試通過了並不意味着程序就沒有bug了,但是不通過程序肯定有bug。

文檔測試

函數或者模塊裏的文檔註釋有一些樣例代碼,文檔測試就可以將這些樣例代碼抽取出來放在交互式的python環境下運行看是否和預期結果一致。
如果是遇到異常的話應該在文檔註釋裏這麼寫,其中用...表示一大串異常信息。

>>> fact("abc")
Traceback (most recent call last):
...
TypeError: 必須傳入整數 參數是abc

下面代碼就展示瞭如何用文檔測試測試自己寫的fact方法的正確性。
如何運行文檔測試呢?可以參考下面代碼的最後三行,導入了doctest模塊。

# -*- coding: UTF-8 -*-


def fact(n):
    '''
    計算n對應的階乘
    >>> fact(1)
    1
    >>> fact(5)
    120
    >>> fact("abc")
    Traceback (most recent call last):
    ...
    TypeError: 必須傳入整數 參數是abc
    >>> fact(-1)
    Traceback (most recent call last):
     ...
    ValueError: 不能小於1
    >>>
    :param
    :return
    '''
    if not isinstance(n, int):
        raise TypeError("必須傳入整數 參數是{}".format(n))
    if n < 1:
        raise ValueError("不能小於1")
    if n == 1:
        return 1
    return n * fact(n-1)


if __name__=='__main__':
    import doctest
    doctest.testmod()

在pycharm中鼠標點擊到文檔測試代碼部分然後右鍵即可運行文檔測試。
在這裏插入圖片描述

在命令行中輸入python test.py即可運行文檔測試,如果什麼信息都沒有就表示文檔測試通過。如果出錯了就應該有信息(下面的截圖中我故意寫錯來展示錯誤信息)
在這裏插入圖片描述

參考網址

廖雪峯老師的python教程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章