Python學習筆記__8.3章 單元測試
1、概覽
單元測試是用來對一個模塊、一個函數或者一個類來進行正確性檢驗的測試工作。
比如對函數abs(),我們可以編寫出以下幾個測試用例:
輸入正數,比如1、1.2、0.99,期待返回值與輸入相同;
輸入負數,比如-1、-1.2、-0.99,期待返回值與輸入相反;
輸入0,期待返回0;
輸入非數值類型,比如None、[]、{},期待拋出TypeError。
把上面的測試用例放到一個測試模塊裏,就是一個完整的單元測試。
單元測試的意義:
方便的檢測代碼
在修改代碼的時候,可以極大程度地保證該模塊行為仍然是正確的。
1.1、編寫一個測試單元
編寫一個Dict類,這個類的行為和dict
#mydict.py
class Dict(dict):
def __init__(self, **kw): # 初始化實例,可以傳入任意個關鍵字參數,即鍵值對
super().__init__(**kw) # super()無參數,是按mro表,調用父類的方法。這裏是將**kw交給父類dict 初始化
def __getattr__(self, key): # Dict沒有設置初始屬性,所以 Dict()每使用一個屬性,會用到__getattr__方法
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
# 繼承的父類是unittest.TestCase
# 它提供了很多內置的條件判斷,我們只需要調用這些方法就可以斷言輸出是否是我們所期望的
class TestDict(unittest.TestCase):
def test_init(self): # 測試Dict能否正常創建實例,並獲取到實例的屬性
d = Dict(a=1, b='test')
self.assertEqual(d.a, 1) #斷言d.a 的返回結果與1相等。相等才能繼續執行
self.assertEqual(d.b, 'test')
self.assertTrue(isinstance(d, dict))
def test_key(self): # Dict() 能否正確添加屬性
d = Dict()
d['key'] = 'value'
self.assertEqual(d.key, 'value')
def test_attr(self): # Dict() 能否正確獲取屬性的value
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值時,期待報錯
value = d['empty']
def test_attrerror(self): # 錯誤測試,同上,獲取屬性value值得方法不同,拋出的錯誤不同
d = Dict()
with self.assertRaises(AttributeError): # 獲取不存在的屬性的value值時,期待報錯
value = d.empty
總結:
以test開頭的方法就是測試方法,不以test開頭的方法不被認為是測試方法,測試的時候不會被執行。
self.assertEqual(abs(-1), 1) # 斷言函數返回的結果與1相等
期待拋出指定類型的Error,比如通過d['empty']訪問不存在的key時,斷言會拋出KeyError
with self.assertRaises(KeyError):
value = d['empty']
運行單元測試
運行方式一:
在mydict_test.py的最後加上兩行代碼,這樣可以把mydict_test.py當做正常的python腳本運行
if __name__ == '__main__':
unittest.main()
$ python mydict_test.py
運行方式二:
在命令行通過參數-m unittest直接運行單元測試,這樣可以一次批量運行很多單元測試
$ python -m unittest mydict_test
1.2、setUp與tearDown
setUp()和tearDown()方法。會分別在每調用一個測試方法的前後分別被執行
用法:
如果你的測試需要啟動一個數據庫,就可以在setUp()方法中連接數據庫,在tearDown()方法中關閉數據庫,這樣,不必在每個測試方法中重復相同的代碼:
class TestDict(unittest.TestCase):
def setUp(self):
print('setUp...')
def tearDown(self):
print('tearDown...')
2、super() 函數
我們在編寫Dict類時。用到了super(),並用其調用了父類dict的方法。在此,我們詳解supper()。
super()的功能就是調用父類函數的方法,並且保證每個父類函數只調用一次(如果每個類都使用super)
2.1、super()的入門與使用
在類的繼承中,如果重定義某個方法,該方法會覆蓋父類的同名方法,但有時,我們希望能同時實現父類的功能,這時,我們可以用super()調用父類的方法。
class Animal(object): # 定義了一個Animal 類
def __init__(self, name):
self.name = name
def greet(self): # 定義了一個greet() 方法
print 'Hello, I am %s.' % self.name
class Dog(Animal): #定義了一個Dog類,父類是Animal
def greet(self): # 也定以了一個greet() 方法,但這個方法是調用Animal的方法
super(Dog, self).greet() # 將Dog創建的實例名,按mro 表的順序,傳給Dog的下一個父類,調用該父類的方法
print 'WangWang...'
# 調用結果
>>> dog = Dog('dog')
>>> dog.greet()
Hello, I am dog. # 父類方法
WangWang.. # 子類方法
2.2、MRO列表
雖然super()調用父類的方法,但卻和父類沒有實質性的關聯。它的調用順序是根據MRO表來的
對於你定義的每一個類,Python 會計算出一個方法解析順序(Method Resolution Order, MRO)列表,它代表了類繼承的順序
>>> Dog.mro() #查看Dog類的 MRO表
[<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>]
>>> C.mro() # Base是[A、B]的父類,[A、B]是C的父類
[__main__.C, __main__.A, __main__.B, __main__.Base, object]
MRO 列表的順序通過一個 C3 線性化算法來實現的。總的來說,一個類的 MRO 列表就是合並所有父類的 MRO 列表,並遵循以下三條原則:
子類永遠在父類前面
如果有多個父類,會根據它們在列表中的順序被檢查
如果對下一個類存在兩個合法的選擇,選擇第一個父類
2.3、super工作原理
super工作原理如下:
def super(cls, inst): # cls表類,inst是cls創建的實例。獲 cls 在 inst 的 MRO 列表中的下一個類。
mro = inst.__class__.mro() # 獲取inst的mro 表
return mro[mro.index(cls) + 1] #返回表中當前cls的下標,並+1,即返回當前cls的下一個父類
Python: 你不知道的 super (http://python.jobbole.com/86787/)
Python學習筆記__8.3章 單元測試