1. 程式人生 > 程式設計 >python錯誤除錯及單元文件測試過程解析

python錯誤除錯及單元文件測試過程解析

這篇文章主要介紹了python錯誤除錯及單元文件測試過程解析,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

錯誤分為程式的錯誤和由使用者錯誤的輸入引起的錯誤,此外還有因為各種各樣意外的情況導致的錯誤,比如在磁碟滿的時候寫入、從網路爬取東西的時候,網路斷了。這類錯誤稱為異常

錯誤處理

普通的錯誤處理機制就是在出錯的時候返回一個錯誤程式碼,但是這樣十分不方便,一是因為錯誤碼是和正常結果一樣的方式返回的,判斷起來十分不方便,二是錯誤還需要一級一級的向上報,直到錯誤處理程式。

所以高階語言通常都內建了一套 try...except...finally... 的錯誤處理機制,Python也不例外。

try:
  A#如果A中的程式碼執行過程中出錯,就會執行B中的程式碼
except ZeroDivisionError as e:
  B
finally:
  C#C中的程式碼無論是否出錯都會正常執行(可以不要這個)<br>。。。

如果錯誤有不同的型別,可以說使用多個except語句,每個語句處理一個型別的錯誤

另外,可以在except後面加一個else,如果沒有出錯,會執行else

Python 的錯誤其實也是一個類,所有的異常型別都是從BaseException類派生的

except在捕獲錯誤時,不但捕獲該型別的錯誤,而且還會把子類一網打盡

try:
  foo()
except ValueError as e:
  print('ValueError')
except UnicodeError as e:
  print('UnicodeError')
#第二個except永遠也捕獲不到UnicodeError,因為UnicodeError是ValueError的子類,如果有,也被第一個except給捕獲了。

使用try...except還有一個巨大的好處,就是可以跨越多層呼叫,比如函式main()呼叫foo(),foo()呼叫bar(),結果bar()出錯了,這時,只要main()捕獲到了,就可以處理。也就是說,不需要在每個可能出錯的地方去捕獲錯誤,只要在合適的層次去捕獲錯誤就可以了。這樣一來,就大大減少了寫try...except...finally的麻煩。

記錄錯誤

如果不捕獲錯誤,自然可以讓Python直譯器來打印出錯誤堆疊,但程式也被結束了。既然我們能捕獲錯誤,就可以把錯誤堆疊打印出來,然後分析錯誤原因,同時,讓程式繼續執行下去。

Python內建的logging模組可以非常容易地記錄錯誤資訊

通過配置,logging還可以把錯誤記錄到日誌檔案裡,方便事後排查。

丟擲錯誤

因為錯誤是class,捕獲一個錯誤就是捕獲到該class的一個例項。因此,錯誤並不是憑空產生的,而是有意建立並丟擲的。Python的內建函式會丟擲很多型別的錯誤,我們自己編寫的函式也可以丟擲錯誤。

如果要丟擲錯誤,首先根據需要,可以定義一個錯誤的class,選擇好繼承關係,然後,用raise語句丟擲一個錯誤的例項

只有在有必要的時候才定義我們自己的錯誤

另外一種錯誤處理

在try...excep捕獲到異常後,還可以在except中使用 'raise‘把異常丟擲去,以便於上級處理,如果raise語句不帶引數,就會把異常原樣丟擲去,我們還可以通過raise 跟一個別的異常型別來將一種錯誤的型別轉化為另外一種型別如:

try:
  10 / 0
except ZeroDivisionError:
  raise ValueError('input error!')

這種型別應該是一種合理的型別,而不應該將一種型別轉化為另外一種不相干的型別

程式也可以主動丟擲錯誤,讓呼叫者來處理相應的錯誤。但是,應該在文件中寫清楚可能會丟擲哪些錯誤,以及錯誤產生的原因。  

除錯

斷言

我們有事再除錯的時候為了省事,就直接由print打印出變數的值,斷言的作用和上面一樣,凡是可以用print來輔助檢視的地方,都可以用斷言替代

斷言可以加提示資訊,

def foo(s):
  n = int(s)
  assert n != 0,'n is zero!'#檢查n是否是0,返回bool
  return 10 / n
 
def main():
  foo('0')

如果斷言失敗,assert語句本身就會丟擲AssertionError:提示資訊

啟動Python直譯器時可以用-O引數來關閉assert:

$ python -O err.py

使用pdb方式來除錯

python -m pdb fortest.py#使用-m pdb 來啟動除錯
l #使用l來檢視程式碼
n #使用n來執行一行程式碼
p 變數名#任何時候都可以輸入p加變數名來檢視變數
q#使用q退出

pdb.set_trace()

這個方法也是用pdb,但是不需要單步執行,我們只需要import pdb,然後,在可能出錯的地方放一個pdb.set_trace(),就可以設定一個斷點:

執行程式碼,程式會自動在pdb.set_trace()暫停並進入pdb除錯環境,可以用命令p檢視變數,或者用命令c繼續執行:

IDE

雖然用IDE除錯起來比較方便,但是最後你會發現,logging才是終極武器。

單元測試

為什麼編寫單元測試呢,因為在寫好的程式可能在以後還需要修改,這時如果由單元測試,我們就能夠保證修改後的程式在功能上和以前的相同,這一定程度上也減少了測試的繁雜性

這種以測試為驅動的開發模式最大的好處就是確保一個程式模組的行為符合我們設計的測試用例。在將來修改的時候,可以極大程度地保證該模組行為仍然是正確的。

接下來,作者舉了一個例子來介紹了單元測試的編寫模式,並且介紹了一些用到的函式

我們需要引入Python自帶的測試模組unittest模組

import unittest

編寫單元測試的時候,需要編寫一個測試類,這個類從unittest.TestCase派生

def TestDict(unittest.TestCase):
  def test_init(self):
    pass

以test開頭的方法就是測試方法,不以test開頭的方法就不被認為是測試方法,執行單元測試的時候不會被執行

對每一類測試都需要編寫一個測試方法,由於unittest.TestCase內建了很多判斷,我們只需要斷言這些輸出是否是我們所需要的,最常用的斷言就是assertEqual(),

self.assertEqual(abs(-1),1) # 斷言函式返回的結果與1相等
另一種重要的斷言就是期待丟擲指定型別的Error,比如通過d['empty']訪問不存在的key時,斷言會丟擲KeyError:

with self.assertRaises(KeyError):
  value = d['empty']

執行單元測試

兩種方法,一種直接在模組中加入

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

另一種方法是在命令列通過引數-m unittest直接執行單元測試

這是推薦的做法,因為這樣可以一次批量執行很多單元測試,並且,有很多工具可以自動來執行這些單元測試。

setUp和tearDown

這兩個函式可以寫在測試類中,作用就是再每個測試方法被呼叫之前會執行setUp(),被呼叫之後會執行tearDown(),可以把一些準備工作、和善後工作放到這些函式中。

  • 單元測試可以有效地測試某個程式模組的行為,是未來重構程式碼的信心保證。
  • 單元測試的測試用例要覆蓋常用的輸入組合、邊界條件和異常。
  • 單元測試程式碼要非常簡單,如果測試程式碼太複雜,那麼測試程式碼本身就可能有bug。
  • 單元測試通過了並不意味著程式就沒有bug了,但是不通過程式肯定有bug。

文件測試

文件測試就是執行寫在註釋中的例項程式碼

文件測試不能再除錯(Debugger)模式下執行,否則會報錯

PYDEV DEBUGGER WARNING:
sys.settrace() should not be used when the debugger is being used.
This may cause the debugger to stop working correctly.
If this is needed,please check:
http://pydev.blogspot.com/2007/06/why-cant-pydev-debugger-work-with.html
to see how to restore the debug tracing back correctly.
Call Location:
 File "c:\users\administrator.sc-201605202132\appdata\local\programs\python\python36\Lib\doctest.py",line 1480,in run
  sys.settrace(save_trace)

很多文件都有示例程式碼,可以把這些示例程式碼在Python的互動環境下執行。這些程式碼與其他說明可以寫在註釋中,然後,由一些工具來自動生成文件

def abs(n):
  '''
  Function to get absolute value of number.
   
  Example:
   
  >>> abs(1)
  1
  >>> abs(-1)
  1
  >>> abs(0)
  0
  '''
  return n if n >= 0 else (-n)

無疑更明確地告訴函式的呼叫者該函式的期望輸入和輸出。並且,Python內建的“文件測試”(doctest)模組可以直接提取註釋中的程式碼並執行測試。

doctest嚴格按照Python互動式命令列的輸入和輸出來判斷測試結果是否正確。只有測試異常的時候(即真正執行的結果和例項程式碼中的結果不一樣的時候,就會報錯),可以用...表示中間一大段煩人的輸出。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。