竹風看的第一本有關Python的書是《Dive Into Python》(簡稱DIP),本人覺得這本書寫得是相當不錯的(當然竹風無意捲入關於這本書是好是壞的爭論,只要找到適合自己的資料和學習方式就好)。讀《DIP》的時候,竹風對HTML和XML是一竅不通(當然現在也只是會點基本的),所以這兩章是看得雲裡霧裡的。當然也有給竹風帶來震撼的章節,比如“單元測試”和“測試優先編程”。
《DIP》中給出了一個羅馬數位程式例子,裡面用到了Python中的unittest模組,這段時間竹風看了下Python 2.7的文檔,結合自己的工作體會,做一個簡單總結,跟大家分享一下。當然,如果錯誤,是竹風學藝不精,歡迎大家的指正和討論^_^。
參考資料:《Dive Into Python》 Mark Pilgrim 第13章.單元測試 第14章.測試優先編程 (強力推薦讀一下,裡面測試驅動開發的思想很值得瞭解)
《ActivePython Documentation》 這個是在windows上安裝 ActiveState ActivePython 2.7 附帶的
一、簡單的例子
竹風琢磨著,程式員最好的交流就是用代碼了,“翠花,上代碼~~”
1 #!/usr/bin/env python 2 #coding: utf-8 3 4 import random 5 import unittest #首先將 unittest 模組匯入 6 7 class TestSequenceFunctions(unittest.TestCase): #繼承了TestCase 8 9 def setUp(self): #初始化函數,產生10個元素的列表10 self.seq = range(10)11 12 def test_shuffle(self):13 #測試shuffled操作是否會丟失元素14 random.shuffle(self.seq)15 self.seq.sort()16 self.assertEqual(self.seq, range(10)) #用到了一個判斷相等的斷言17 18 #如果傳入一個不可變的對象,比如元組,應拋出一個異常19 self.assertRaises(TypeError, random.shuffle, (1,2,3))20 21 def test_choice(self):22 #測試choice操作返回的值是否屬於seq23 element = random.choice(self.seq)24 self.assertTrue(element in self.seq) #用到了一個判斷值為真的斷言25 26 def test_sample(self):27 #測試sample操作返回的結果是否屬於seq28 self.assertRaises( #當傳入的參數有誤時,應拋出一個異常29 ValueError, random.sample, self.seq, 2030 )31 for element in random.sample(self.seq, 5):32 self.assertTrue(element in self.seq) #用到了一個判斷值為真的斷言33 34 if __name__ == '__main__':35 unittest.main()
竹風在文檔的例子上稍微加了點注釋,這個例子對各位園友來說應該是小菜一碟了。如果使用的是Python 2.7的話,還可以將28-30行修改為:
1 with self.assertRaises(ValueError):2 random.sample(self.seq, 20)
運行起來應該是這個樣子的:
1 $ python TestSequenceFuncions.py2 ...3 ----------------------------------------------------------------------4 Ran 3 tests in 0.000s5 6 OK
如果想看稍微詳細點的測試資訊,可以將最後一行 “unittest.main()” 修改如下:
suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions) unittest.TextTestRunner(verbosity=2).run(suite)
這時候輸出資訊就會稍微詳細一點:
$ python TestSequenceFuncions.pytest_choice (__main__.TestSequenceFunctions) ... oktest_sample (__main__.TestSequenceFunctions) ... oktest_shuffle (__main__.TestSequenceFunctions) ... ok----------------------------------------------------------------------Ran 3 tests in 0.000sOK
如果還覺得不夠詳細,那麼可以在命令列上加 -v 選項:
$ python -v TestSequenceFuncions.py
不過這個輸出到螢幕的資訊已經達到刷屏的程度了。。。大家還是慎用。。。
二、測試的順序與命名規範
不知道各位園友注意到了沒有,測試的順序是跟class裡面定義的def的順序無關的。
在class的定義順序是:(test_shuffle,test_choice,test_sample);
而實際測試的時候是:(test_choice,test_sample,test_shuffle);
根據竹風的個人經驗,測試的順序應該是在去掉 “test_” 首碼後,按照字母順序排序。
還需要提到的一點是,需要測試的 def 需要以 “test_” 為首碼。針對這點,我們可以寫一個 “make_test_data” 的函數準備測試資料,再寫一個 “clean_test_data” 的函數清理測試資料。
PS:根據《DIP》裡面提到關於測試的兩個基本原則:
一、每個測試案例只回答一個問題。
二、每個測試案例必須可以與其他測試案例隔離工作,每個測試案例是一個“孤島”。
根據這兩個基本原則,的確應該與定義他們的順序無關的。
三、初始化(setUp)和清理工作(tearDown)
如果竹風用“make_test_data”來建立測試資料,竹風的基友用的是"create_test_data",一千個程式員可能就帶來一千種命名方式,這五花八門的函數名字可就真的讓人頭疼了。
根據Python裡“import this”的精神,應該會有專門的函數來執行初始化或者清理工作的。也的確有這兩個函數,setUp 與 tearDown 分別負責 初始化 和 清理 工作。
setUp函數總是第一個調用,而tearDown函數則總是在最後一個調用。現在函數調用的順序也大概理清楚了:setUp --> test_* --> tearDown。
四、常用的斷言方法(assert methods)
最後水一下幾個常用的斷言。
assertEqual(first, second, msg=None)
assertNotEqual(first, second, msg=None)
判斷first和second是否相等(不等),msg為測試失敗時給出的資訊。msg一般使用預設值就行
assertTrue(expr, msg=None)
assertFalse(expr, msg=None)
判斷expr的值是否為真(假),msg為測試失敗時給出的資訊。
assertRaises(exception, callable, *args, **kwds) #參數魔法裡面有提到過的哦
assertRaises(exception)
判斷是否拋出一個異常,*args和 **kwds 為調用 callable 需要傳的參數,注意這裡callable的傳參數方式。可參照上面的例子。
到這裡竹風這個簡單的分享也結束了,用上面的這些內容,寫個簡單的測試案例問題應該不大了。
測試個人感覺上是一個很大的領域,竹風也只是剛接觸。
建議有興趣的園友看一下《DIP》的13、14章,當然也可以看下Python的文檔。
祝大家新年快樂,萬事如意^_^