轉載
原文地址:http://blog.csdn.net/sasoritattoo/article/details/17448397
在Python中進行單元測試需要用到自動單元測試框架PyUnit,Python2.1及其以后的版本都將PyUnit作為一個標准模塊(即python的unittest模塊),如果你很out,那么你需要從PyUnit網站下載源碼安裝后才能使用。
一、Python單元測試范例
測試最基本的原理是比較預期結果是否與實際執行結果相同,如果相同則測試成功,否則測試失敗。為了更好地理解自動測試框架PyUnit,下面會以對Widget類進行測試為例說明之:
- #widget.py
- #將要被測試的類Widget
- class Widget:
- def __init__(self, size = (40, 40)):
- self._size = size
- def getSize(self):
- return self._size
- def resize(self, width, height):
- if width < 0 or height < 0:
- raise ValueError, "illegal size"
- self._size = (width, height)
- def dispose(self):
- pass
二、測試用例TestCase
軟件測試中最基本的組成單元式測試用例(test case),PyUnit使用TestCase類來表示測試用例,並要求所有用於執行測試的類都必須從該類繼承。TestCase子類實現的測試代碼應該是自包含的(self contained),即測試用例既可以單獨運行,也可以和其它測試用例構成集合共同運行。TestCase類中常用的函數或方法有:
setUp:進行測試前的初始化工作。
tearDown:執行測試后的清除工作。
failedinfo:表示不成立打印信息faliedinfo,為可選參數。
self.assertEqual(value1, value2, failedinfo):會無條件的導致測試失敗,不推薦使用。
self.assertTrue(, failedinfo):斷言value1 == value2。
self.assertFalse(, failedinfo):斷言value為真。
self.assertRaises(ValueError, self.widget.resize, -1, -1):斷言肯定發生異常,如果沒發生異常,則為測試失敗。參數1為異常,參數2為拋出異常的調用對象,其余參數為傳遞給可調用對象的參數。
TestCase在PyUnit測試框架中被視為測試單元的運行實體,Python程序員可以通過它派生自定義的測試過程與方法(測試單元),利用Command和Composite設計模式,多個TestCase還可以組合成測試用例集合。PyUnit測試框架在運行一個測試用例時,TestCase子類定義的setUp()、runTest()和tearDown()方法被依次執行,最簡單的測試用例只需要覆蓋runTest()方法來執行特定的測試代碼就可以了。
1、靜態方法
一個測試用例只對軟件模塊中一個方法進行測試,采用覆蓋runTest()方法來構造測試用例,這在PyUnit中稱之為靜態方法,舉例說明如下:
- #static.py
- from widget import Widget
- import unittest
- #執行測試的類
- class WidgetTestCase(unittest.TestCase):
- def runTest(self):
- widget = Widget()
- self.assertEqual(widget.getSize(), (40, 40))
- #測試
- if __name__ == "__main__":
- testCase = WidgetTestCase()
- testCase.runTest()
如果采用靜態方法,Python程序員就不得不為每個要測試的方法編寫一個測試類,該類通過覆蓋runTest()方法來執行測試,並在每個測試類中生成一個待測試的對象,這樣會非常繁瑣與笨拙。
2、動態方法
鑒於靜態方法的缺陷,PyUnit提供了另一種高帥富的解決方法,即動態方法,只編寫一個測試類來完成對整個軟件模塊的測試,這樣對象的初始化工作可以在setUp()方法中完成,而資源的釋放則可以在tearDown()方法中完成,舉例說明如下:
- #dynamic.py
- from widget import Widget
- import unittest
- class WidgetTestCase(unittest.TestCase):
- def setUp(self):
- self.widget = Widget()
- def tearDown(self):
- self.widget.dispose()
- self.widget = None
- def testSize(self):
- self.assertEqual(self.widget.getSize(), (40, 40))
- def testResize(self):
- self.widget.resize(100, 100)
- self.assertEqual(self.widget.getSize(), (100, 100))
動態方法不再覆蓋runTest()方法,而是為測試類編寫多個測試方法,按照慣例這些方法通常以test開頭但這不是必須的,在創建TestCase子類的實例時必須給出測試方法的名稱來為PyUnit測試框架指明運行該測試用例時應該調用測試類中的哪些方法,這通常會結合測試用例集TestSuite一起使用。
三、測試用例集TestSuite
完整的單元測試很少只執行一個測試用例,開發人員通常需要編寫多個測試用例才能對某一軟件功能進行比較完成的測試,這些相關的測試用例稱為一個測試用例集,在PyUnit中是用TestSuite類來表示的。PyUinit測試框架允許Python程序員在單元測試代碼中定義一個名為suite()的全局函數,並將其作為整個單元測試的入口,PyUnit通過調用它來完成整個測試過程:
- def suite():
- suite = unittest.TestSuite()
- suite.addTest(WidgetTestCase("testSize"))
- suite.addTest(WidgetTestCase("testResize"))
- return suite
也可以直接定義一個TestSuite的子類,並在其初始化方法__init__中完成所有測試用例的添加:
- class WidgetTestSuite(unittest.TestSuite)
- def __init__(self):
- unittest.TestSuite.__init__(self, map(WidgetTestCase, ("testSize", "testResize")))
這樣只需要在suite()方法中返回該類的一個實例就可以了:
- def suite():
- return WidgetTestSuite()
在PyUnit測試框架中,TestSuite類可以看成是TestCase類的一個容器,用來對多個測試用例進行組織,這樣多個測試用例可以自動在一次測試中全部完成。事實上,TestSuite除了可以包含TestCase外,也可以包含TestSuite,從而可以構成一個更龐大的測試用例集:
suite1 = mysuite1.TheTestSuite()
suite2 = mysuite2.TheTestSuite()
alltests = unittest.TestSuite((suite1, suite2))
四、實施測試TestRunner
編寫測試用例(TestCase)並將它們組織成測試用例集(TestSuite)的最終目的只有一個:實施測試並獲得最終結果。PyUnit使用TestRunner類作為測試用例的基本執行環境,來驅動整個單元測試過程。但是Python開發人員在進行單元測試時一般不直接使用TestRunner類,而是使用其子類TextTestRunner來完成測試,並將測試結果以文本方式顯示出來。舉例說明如下:
- #text_runner.py
- from widget import Widget
- import unittest
- #執行測試的類
- class WidgetTestCase(unittest.TestCase):
- def setUp(self):
- self.widget = Widget()
- def tearDown(self):
- self.widget.dispose()
- self.widget = None
- def testSize(self):
- self.assertEqual(self.widget.getSize(), (40, 40))
- def testResize(self):
- self.widget.resize(100, 100)
- self.assertEqual(self.widget.getSize(), (100, 100))
- #測試
- if __name__ == "__main__":
- #構造測試集
- suite = unittest.TestSuite()
- suite.addTest(WidgetTestCase("testSize"))
- suite.addTest(WidgetTestCase("testResize"))
- #執行測試
- runner = unittest.TextTestRunner()
- runner.run(suite)
使用如下命令執行該單元測試:
$python text_runner.py
默認情況下,TextTestRunner將結果輸出到sys.stdout/sys.stderr上,但是如果在創建TextTestRunner類實例時將一個文件對象傳遞給了構造函數,則輸出結果將被重定向到該文件中。
五、大道至簡main()
PyUnit模塊中定義了一個名為main的全局方法,使用它可以很方便地將一個單元測試模塊變成可以直接運行的測試腳本,main()方法使用TestLoader類來搜索所有包含在該模塊中的測試方法,並自動執行它們。如果Python程序員能夠按照約定(以test開頭)來命令所有的測試方法,那么只需要在測試模塊的最后加入如下幾行代碼即可:
- if __name__ == "__main__":
- unittest.main()
下面是利用main()方法來進行測試的完整例子:
- #main_runner.py
- from widget import Widget
- import unittest
- #執行測試的類
- class WidgetTestCase(unittest.TestCase):
- def setUp(self):
- self.widget = Widget()
- def tearDown(self):
- self.widget.dispose()
- self.widget = None
- def testSize(self):
- self.assertEqual(self.widget.getSize(), (40, 40))
- def testResize(self):
- self.widget.resize(100, 100)
- self.assertEqual(self.widget.getSize(), (100, 100))
- #測試
- if __name__ == "__main__":
- unittest.main()
使用如下命令執行上面的單元測試:
$python main_runner.py
如上這樣將執行WidgetTestCase中的所有測試方法,但是如果只想執行testSize()方法,則可以如下這般:
$python main_runner.py WidgetTestCase.testSize
如果在單元測試腳本中定義了TestSuite,還可以指定要運行的測試集,使用-h參數可以查看運行該腳本所有可能用到的參數:
$python main_runner.py -h
需要注意的是:PyUnit的TestCase中如果有多個test_xxx,則默認按照xxx的字母順序執行測試用例函數,如果test_xxx之間有依賴關系的話就會出錯,解決方法有二:1、解耦;2、編寫xxx函數時人為地按字母順序。
當然,如果你安裝了Python 2.7.2及以上版本,你還可以利用discover函數來自動發現並執行測試用例:
$python2.7 -m unittest discover
更多關於Python單元測試的資料可以參看這里,還有這里。