【Python入門】19.調(diào)試器pdb、單元測試unittest和文檔測試doctest

筆記更新于2019年12月4日枉长,
摘要:各種調(diào)試方法介紹assert位衩、logging裆蒸、調(diào)試器pdb;單元測試unittest的編寫方法糖驴、如何運行單元測試僚祷;文檔測試doctest的編寫


寫在前面:為了更好的學(xué)習(xí)python,博主記錄下自己的學(xué)習(xí)路程贮缕。本學(xué)習(xí)筆記基于廖雪峰的Python教程辙谜,如有侵權(quán),請告知刪除感昼。歡迎與博主一起學(xué)習(xí)Pythonヽ( ̄▽ ̄)?


目錄

調(diào)試與測試
調(diào)試
? print
? 斷言 assert
? logging
? 調(diào)試器 pdb
? pdb.set_trace( )
? IDE
單元測試 unittest
? 單元測試編寫
? 單元測試方法
? 運行單元測試
? setUp( )和tearDown( )
文檔測試 doctest

調(diào)試與測試

調(diào)試

程序編寫的過程中會出現(xiàn)各種意想不到的bug装哆,想要一次性寫好并成功運行幾乎不可能。我們需要知道在運行過程中哪些變量可能會出錯定嗓,在編寫過程中要有一套調(diào)試程序的手段來修復(fù)bug蜕琴。下面介紹在Python中常見的調(diào)試手段。

? print

最簡單粗暴的方法是print宵溅,只要在把可能會出錯的變量打印出來即可凌简。

def fn(s):
    n = s
    print('>>> n = %d' % n)
    return 10 / n

def main():
    fn(0)

main()

>>> n = 0 
Traceback (most recent call last): 
...
ZeroDivisionError: division by zero 

這樣我們就能知道是n = 0導(dǎo)致的錯誤。這種方法有個很大的問題就是恃逻,在程序編寫完之后會留下大量的垃圾信息雏搂,不好處理。

? 斷言 assert

在上面凡是用到print的地方都可以用assert來代替寇损。

assert后面加一個判斷語句凸郑,該判斷為正確時程序正常運行,反之出現(xiàn)錯誤润绵,打印緊跟的字符串线椰。

def fn(s):
    n = s
    assert n != 0, '>>> n = 0'
    return 10 / n

def main():
    fn(0)
    
main()

運行結(jié)果:

Traceback (most recent call last): 
...
AssertionError: >>> n = 0 

assert 即聲明胞谈、斷言n應(yīng)該不等于0尘盼,但結(jié)果n等于0,則斷言失敗烦绳,拋出AssertionError錯誤卿捎,并打印出緊跟的字符串'>>> n = 0'。

相比print径密,assert可以通過-O參數(shù)來關(guān)閉午阵,關(guān)閉之后所有的assert語句相當(dāng)于pass。如把上面代碼保存為err.py文件,在python解釋器中運行:

python -O err.py

運行結(jié)果:

Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero

可見assert被關(guān)閉了底桂,解釋器打印出了ZeroDivisionError而不是AssertionError植袍。

? logging

logging也是可以把錯誤輸出。

import logging

s = 0
n = s
logging.info('n = %d' % n)
print(10 / n)

運行之后發(fā)現(xiàn)出了ZeroDivisionError之外沒有其他信息籽懦。這是因為logging的信息輸出是有級別限制的于个,我們需要設(shè)置級別。

在import logging之后加上:

logging.basicConfig(level=logging.INFO)

這時候再運行:

INFO:root:n = 0
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

顯示的錯誤信息就有l(wèi)ogging.info之后的信息了暮顺。

logging的好處是允許你指定記錄信息的級別厅篓。一共有五個級別,從小到大依次是:debug捶码,info羽氮,warning,error惫恼,critical档押。默認的level是warning,只有高于等于warning級別的信息才會被打印祈纯,當(dāng)然可以修改level汇荐,這就是為什么一開始沒有打印信息。

logging的另一個好處是可以通過簡單的配置盆繁,把信息輸出到文件中(后面會介紹如何配置)掀淘。

? 調(diào)試器 pdb

pdb,即python debugger,Python調(diào)試器油昂。調(diào)試器可以讓程序逐步運行革娄,并查看運行狀態(tài)。

先寫一個簡單的py文件:

# err.py
s = '0'
n = int(s)
print(10 / n)

在命令行模式以參數(shù)-m pdb啟動err.py:

python -m pdb err.py

啟動pdb之后冕碟,會自動定位到下一步要執(zhí)行的代碼“s = '0'”拦惋,然后在(pdb)之后等待輸入。

輸入字母l命令可以查看err.py的全部代碼:

(Pdb) l
  1     # err.py
  2  -> s = '0'
  3     n = int(s)
  4     print(10 / n)

輸入字母n可以單步執(zhí)行代碼:

(Pdb) n
> c:\users\administrator\err.py(3)<module>()
-> n = int(s)
(Pdb) n
> c:\users\administrator\err.py(4)<module>()
-> print(10 / n)

輸入字母p加變量名可以查看該變量:

(Pdb) p s
'0'
(Pdb) p n
0

輸入字母q結(jié)束調(diào)試:

(Pdb) q
? pdb.set_trace( )

set_trace即放置斷點安寺。在可能出錯的地方添加語句pdb.set_trace()就可以設(shè)置一個斷點

# err.py
import pdb

s = '0'
n = int(s)
pdb.set_trace()                                # 運行到這里會自動暫停
print(10 / n)

當(dāng)代碼運行到pdb.set_trace()時厕妖,就會暫停并自動進入pdb調(diào)試環(huán)境√羰可執(zhí)行上面介紹的命令進行調(diào)試言秸。輸入c繼續(xù)運行。

這種方法比pdb的單步調(diào)試效率要高一點迎捺。

? IDE

Integrated Development Environment举畸,集成開發(fā)環(huán)境,即IDE凳枝。用于程序開發(fā)環(huán)境的應(yīng)用程序抄沮。

IDE一般包括代碼編輯器、編譯器、調(diào)試器和圖形用戶界面等工具叛买。集成了代碼編寫功能砂代、分析功能、編譯功能率挣、調(diào)試功能等一體化的開發(fā)軟件服務(wù)套泊藕。

目前比較好的python IDE有以下幾種:
pycharm
Eclipse + PyDev
Visual Studio + PTVS

單元測試 unittest

單元測試是用來對一個模塊、一個函數(shù)或者一個類來進行正確性檢驗的測試工作难礼。

比如我們想要測試abs函數(shù)娃圆,給出下面的測試用例:

輸入正數(shù),1蛾茉、100讼呢、0.1,期待輸出1谦炬、10悦屏、0.1;
輸入負數(shù)键思,-1础爬、-100、-0.1吼鳞,期待輸出1看蚜、10、0.1赔桌;
輸入0供炎,期待輸出0;
輸入非數(shù)值類型疾党,'a'音诫、[ ]、{ }雪位,期待輸出TypeError

如果abs函數(shù)通過了上面的測試竭钝,我們就認為abs函數(shù)是能夠正常運行的。

在編寫程序時雹洗,有一個測試用例香罐,讓程序的行為始終符合測試用例的邏輯,那么就能極大可能地保證程序的正確性队伟。

下面引用廖雪峰官方網(wǎng)站里面的一個例子穴吹,來介紹Python中單元測試的編寫幽勒。

? 單元測試編寫

如我們想編寫一個Dict類嗜侮,用途與內(nèi)置的dict一樣,但可以通過屬性來訪問,像這樣:

>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d.a
1

于是編寫一個mydict:

class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)                      # Dict類的實例屬性繼承dict

    def __getattr__(self, key):                     # 當(dāng)訪問實例key屬性锈颗,而沒有key屬性時顷霹,調(diào)用該方法
        try:
            return self[key]                        # 嘗試返回鍵key對應(yīng)的值value
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)   
                                                    # 若返回失敗,顯示Dict沒有該屬性

    def __setattr__(self, key, value):              # 設(shè)置一個賦值方法setattr
        self[key] = value           

然后開始編寫單元測試模塊mydict_test.py击吱。

首先我們需要引入Python自帶的unittest模塊和編寫DIct模塊淋淀,然后定義一個測試類,這個類繼承unittest.TestCase

import unittest

from mydict import Dict

class TestDice(unittest.TestCase):
    pass
? 單元測試方法

接下來是寫測試方法覆醇,對每一類測試都需要編寫一個test_xxx()方法朵纷,以test開頭的方法是測試方法,不以test開頭的方法不被認為是測試方法永脓,測試的時候是不會被執(zhí)行袍辞。

在繼承的unittest.TestCase中有許多內(nèi)置的條件判斷方法,我們直接調(diào)用來進行測試常摧。下面是三個常用的測試方法:

assertEqual( )傳入兩個參數(shù)搅吁,一個是需要執(zhí)行的對象,一個是對象執(zhí)行后期望返回的結(jié)果落午。

self.assertEqual(abs(-1), 1)                   # 斷言函數(shù)返回的結(jié)果與1相等

assertTrue( )谎懦,期望括號里面返回的值為True。

self.assertTrue(abs(-1) == 1)

assertRaises( ):溃斋,期望冒號后的語句拋出括號里面的錯誤界拦。

with self.assertRaises(TypeError):
    abs('a')

我們用上面的三種測試方法來編寫mydict_test.py (以下代碼部分除注釋外轉(zhuǎn)自廖雪峰的官方網(wǎng)站)

import unittest

from mydict import Dict

class TestDict(unittest.TestCase):

    def test_init(self):                          # 測試Dict的實例
        d = Dict(a=1, b='test')                       # 創(chuàng)建一個Dict的實例
        self.assertEqual(d.a, 1)                      # 測試是否能通過屬性來訪問值
        self.assertEqual(d.b, 'test')                 # 測試是否能通過屬性來訪問值
        self.assertTrue(isinstance(d, dict))          # 測試實例d是否為dict類

    def test_key(self):                           # 測試key屬性
        d = Dict()                                    # 創(chuàng)建一個Dict的實例
        d['key'] = 'value'                            # 給d添加鍵key和值value
        self.assertEqual(d.key, 'value')              # 測試能否通過屬性key來訪問值value

    def test_attr(self):                          # 測試setattr方法
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')

    def test_keyerror(self):                      # 測試訪問不存在的key時,打印KeyError
        d = Dict()
        with self.assertRaises(KeyError):
            value = d['empty']

    def test_attrerror(self):                     # 測試通過屬性訪問不存在的key時梗劫,打印AttributeError
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty

自此我們完成了單元測試的編寫寞奸,接下來就可以運行單元測試了。

? 運行單元測試

運行單元測試有兩種方法在跳,一種是在單元測試模塊最后加上兩行代碼:

if __name__ == '__main__':
    unittest.main()

然后直接運行mydict_test.py即可枪萄。

另一種是在命令行模式中通過參數(shù)-m unittest運行單元測試:

python -m unittest mydict_test
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

推薦后一種方法,因為這樣可以批量運行多種單元測試猫妙。

通過測試的話就會打印出“OK”瓷翻。

? setUp( )和tearDown( )

補充兩個單元測試方法setUp( )和tearDown( )。這兩個方法會分別在每調(diào)用一個測試方法的前后分別被執(zhí)行割坠。如在上面的單元測試中齐帚,我們加上這兩個方法:

class TestDict(unittest.TestCase):

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

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

輸出結(jié)果就變成:

python -m unittest mydict_test
.....
setUP...
tearDowm...
setUP...
tearDowm...
setUP...
tearDowm...
setUP...
tearDowm...
setUP...
tearDowm...
.
-------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

可見每個方法都前后執(zhí)行了一次setUp( )和tearDown( )。

這有什么用呢彼哼?比如我們的測試方法是需要連接數(shù)據(jù)庫对妄,每個測試方法都要添進行連接與關(guān)閉數(shù)據(jù)庫的操作就很麻煩,這時候可以通過setUp( )來連接數(shù)據(jù)庫和tearDown( )來關(guān)閉數(shù)據(jù)庫敢朱,從而減少了許多重復(fù)的代碼剪菱。

文檔測試 doctest

Python中內(nèi)置的“文檔測試”(doctest)模塊可以直接提取注釋中的代碼并執(zhí)行測試摩瞎。

比如我們寫這樣一段代碼:

def abs(n):
    '''
    Function to get absolute value of number.

    Example:

    >>> abs(1)
    1
    >>> abs(-1)
    1
    >>> abs(0)
    0
    >>> abs('a')
    Traceback (most recent call last): 
    ...
    TypeError: bad operand type for abs(): 'str'
    '''
    return n if n >= 0 else (-n)
    
    

通過單引號'''與‘’’括起的內(nèi)容是注釋,此時我們可以通過doctest模塊來進行測試孝常。

doctest會嚴(yán)格按照Python交互式命令行的輸入和輸出來判斷測試結(jié)果是否正確旗们。

遇到>>>時即開始執(zhí)行代碼,有指示符>>>為輸入构灸,沒有指示符>>>的為輸出上渴。當(dāng)測試異常時,可用...來表示其中的輸出喜颁。

我們來編寫test.py并進行文檔測試稠氮。

# -*- coding: utf-8 -*-

def fact(n):

    '''
    Calculate 1*2*...*n              
    
    >>> fact(1)                               #這里輸入fact(1)
    1                                         #期待輸出的值為1
    >>> fact(10)
    3628800
    >>> fact(-1)
    Traceback (most recent call last):        #當(dāng)遇到錯誤時期待輸出的錯誤信息
    ...
    ValueError
    
    '''
    if n < 1:
        raise ValueError()
    if n == 1:
        return 1
    return n * fact(n - 1)

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

需要注意的是,在第一個>>>之前可以進行函數(shù)的描述半开,在>>>之后的內(nèi)容就會被doctest執(zhí)行括袒。

最后三行代碼表示,只有在命令行直接運行該文件時會進行測試稿茉,而被引用時不會進行測試锹锰,所以不必擔(dān)心doctest會在非測試環(huán)境下運行。

運行這個文件會發(fā)現(xiàn)什么都沒有輸出漓库,這就說明編寫的doctest運行都是正確的恃慧。如果測試代碼與函數(shù)運行結(jié)果不一致,則會出錯渺蒿,比如我們將

>>> fact(10)
    3628800

改為:

>>> fact(10)
    362880

運行結(jié)果:

********************************************************************** 
File "test.py", line 11, in __main__.fact 
Failed example: 
    fact(10) 
Expected: 
    362880 
Got: 
    3628800 
********************************************************************** 
1 items had failures: 
   1 of   3 in __main__.fact 
***Test Failed*** 1 failures. 

顯示fact(10)的期待值為362880痢士,而實際值為3628800。


以上就是本節(jié)的全部內(nèi)容茂装,感謝你的閱讀怠蹂。

下一節(jié)內(nèi)容:20.IO編程

有任何問題與想法,歡迎評論與吐槽少态。

和博主一起學(xué)習(xí)Python吧( ̄▽ ̄)~*

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末城侧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子彼妻,更是在濱河造成了極大的恐慌嫌佑,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侨歉,死亡現(xiàn)場離奇詭異屋摇,居然都是意外死亡,警方通過查閱死者的電腦和手機幽邓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門炮温,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人牵舵,你說我怎么就攤上這事柒啤【牍遥” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵白修,是天一觀的道長妒峦。 經(jīng)常有香客問我重斑,道長兵睛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任窥浪,我火速辦了婚禮祖很,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘漾脂。我一直安慰自己假颇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布骨稿。 她就那樣靜靜地躺著笨鸡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坦冠。 梳的紋絲不亂的頭發(fā)上形耗,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音辙浑,去河邊找鬼激涤。 笑死,一個胖子當(dāng)著我的面吹牛判呕,可吹牛的內(nèi)容都是我干的倦踢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼侠草,長吁一口氣:“原來是場噩夢啊……” “哼辱挥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起边涕,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤般贼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后奥吩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哼蛆,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年霞赫,在試婚紗的時候發(fā)現(xiàn)自己被綠了腮介。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡端衰,死狀恐怖叠洗,靈堂內(nèi)的尸體忽然破棺而出甘改,到底是詐尸還是另有隱情,我是刑警寧澤灭抑,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布十艾,位于F島的核電站,受9級特大地震影響腾节,放射性物質(zhì)發(fā)生泄漏忘嫉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一案腺、第九天 我趴在偏房一處隱蔽的房頂上張望庆冕。 院中可真熱鬧,春花似錦劈榨、人聲如沸访递。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拷姿。三九已至,卻和暖如春旱函,著一層夾襖步出監(jiān)牢的瞬間响巢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工陡舅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抵乓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓靶衍,卻偏偏與公主長得像灾炭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子颅眶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容