16.1先測試 後編碼
16.1.1 精確的需求說明
16.1.2 爲改變而計劃
覆蓋度是測試知識中重要的部分。,優秀的測試程序組的目標之一是擁有良好的覆蓋度,實現這個目標的方法之一是使用覆蓋度工具
16.2測試工具
其中有兩個很棒的模塊可以協助你自動完成測試過程:
1.unitest:通用測試框架
2.doctest:簡單一些的模塊,是檢查文檔用的,但是對於編寫單元測試也很在行。
16.2.1 doctest
例如 假設求數字平方的函數,並且在文檔字符串中添加了一個例子
def square(x):
Squares a number and returns the result.
>>>square(2)
4
>>>square(3)
9
...
return x*x
文檔字符串中也包括了一些文本。這和測試又有什麼關係?假設square函數定義在my_math模塊中。
if __name__=='__main__':
import doctest,my_math
doctest.testmod(my_math)
可以只導入doctest和my_math模塊本身,然後運行doctest中的testmod函數。
$python my_math_.py
16.2.2 unitest
使用unittest框架的簡單測試
import unittest,my_math
class ProductTestCase(unittest.TestCase):
def testIntegers(self):
for x in xrange(-10,10):
for y in xrange(-10,10):
p = my_math.product(x,y)
self.failUnless(p == x*y,'Integer multiplication failed')
def testFloats(self):
for x in xrange(-10,10):
for y in xrange(-10,10):
x = x/10.0
y = y/10.0
p = my_math.product(x,y)
self.failUnless(p == x*y,'Float multiplication failed')
if __name__ == '__main__':unittest.main()
unittest.main函數負責運行測試。它會實例化所有TestCase子類,運行所有名字以test開頭的方法。
如果定義了叫做setUP和tearDown的方法,它們就會在運行每個測試方法之前和之後執行,這樣就可以用這些方法爲所有測試提供一般的初始化和清理代碼,這被稱爲測試夾具。
unittest模塊會區分由異常引發的錯誤和調用failUnless等函數而導致的失敗。下一步就是編寫概要代碼,這樣一來就沒錯誤---只有失敗。這就意味着要創建一個包含下列內容的模塊my_math。
def product(x,y):
pass
下一步讓測試代碼工作
def product(x,y):
return x * y
接下來修改product函數,讓它針對特定的數值7和9失敗。
def product(x,y):
if x==7 and y==9:
return 'An insidious bug has surfaced!'
else:
return x * y
16.3.1 使用PyChecker和PyLint檢查源代碼
pychecker file.py
pylint module
PyChecker和PyLint都可以作爲模塊導入,但是兩者並不是真正爲程序設計的。當導入pychecker.checker時,它會檢查之後的代碼,並且在標準輸出中打印警告。pylint.lint模塊有個叫做Run的非文檔記錄型函數,可以在pylint腳本本身中使用。
使用subprocess模塊調用外部檢查模塊
import unittest,my_math
from subprocess import Popen,PIPE
class ProductTestCase(unittest.TestCase):
def testWithPyCheck(self):
cmd = 'pychecker','-Q',my_math.__file__.rstrip('c')
pychecker = Popen(cmd,stdout=PIPE,stderr=PIPE)
self.assertEqual(pychecker.stdout.read(),'')
def testWithPyLint(self):
cmd = 'pylint','-rm','my_math'
pylint = Popen(cmd,stdout=PIPE,stderr=PIPE)
self.assertEqual(pylint.stdout.read(),'')
if __name__ == '__main__':unittest.main()
上面已經給出了檢查程序的幾個命令行開關,以避免無關的輸出干擾測試:對於pychecker來說,提供了-Q選項,而對於pylint,我提供了-rn(n意爲no)關閉報告,也就是說只顯示警告和錯誤。這裏使用了assertEqual函數以便使從stdout特性讀取的真正輸出顯示在unittest的失敗信息中。
pylint命令會直接同給定名稱的模塊一起運行,所以簡單多了。爲了能讓pycheck工作正常,我們還能獲取一個文件名。使用my_math模塊的__file__屬性獲取這個值,用rstrip剔除任何文件名末尾中可能出現的字符c。
爲了能讓PyLint不出現錯誤,我忽略重寫了my_math模塊。
"""
A simple math module
"""
__revision__ = '0.1'
def product(factor1,factor2):
'The product of two numbers'
return factor1 * factor2
16.3.2 分析
標準庫中已經包含了一個叫做profile的分析模塊。使用分析程序非常簡單,是要使用字符串參數調用它的run方法就行了
>>>import profile
>>>from my_math import product
>>>profile.run('product(1,2)')
如果提供了文件名,比如'my_math.profile'作爲第二個參數來運行,那麼結果就會保存到文件中。可以在之後使用pstats模塊檢查分析結果:
>>>import pstats
>>>p = pstats.Stats('my_math.profile')