筆記更新于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宵溅,只要在把可能會出錯的變量打印出來即可凌简。
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吧( ̄▽ ̄)~*