Python UnitTest快速上手
這篇文章主要說明 Pythin unit Testing 的寫法、
為什麼要將測試程式寫成 unit Testing? 寫成 unit test 的格式有什麼好處?
UnitTest 的好處
當我們將測試程式按照 unitTest的結構寫好之後,就可以有下列好處。
1. 測試程式的執行。可以使用 PyTest 或是 Nose這樣的工具將許多測試程式執行。特別當我們的測試程式散布各處的時候,nose這樣的工具就會自動找到這些測試程式並且執行。(檔名必須是 test_ 開頭或是 _test結尾)。除了檔名的命名之外,筆者也建議可以將測試程式及中放在 ‘test’路徑下,方便日後執行與管理。
2. 測試程式報告。nose 這樣的執行工具都會提供測試報告的輸出。我們不需要再額外開發就可以有美觀的 HTML報告。
3. unitTest 自動將每一個 function 視為一個測試個案。有利於未來測試執行與測試結果報告。建議 function 名稱命名為 test_xxxxx 開頭。如此一來,nose這樣的執行工具也很可以容易的辨識自動執行。
UnitTest 結構
通常一個 unitTest的測試程式結構如下,會有三大段落。
第一段落為 setUP: 這個段落主要用來準備測試資料與環境。每次測試程式執行時,只會在測試程式啟動時執行一次。
中間段落為測試個案: 這個部份就是測試程式與測試個案。每一個 function 就是一個測試個案,都會有一個對應的測試結果。
最後段落為 TearDown: 這個段落通常用來將系統資源釋放,或是將測試環境與資料復原。每次測試程式執行時只會執行一次。
[pastacode lang=”python” message=”” highlight=”” provider=”manual”]
import unittest
class test_myUnitTest(unittest.TestCase):
def setUp(self):
print "****setUP****"
print "This will only run once when start-up the test program"
print "setUP is normally used to prepare testing data/env"
def test_case1(self):
print "****test case 1P****"
print "this is tesitng case 1"
def test_case2(self):
print "****test case 2****"
print "this is tesitng case 2"
def tearDown(self):
print "****tear down****"
print "This will only run once while closing the test program"
print "tearDown is normally used to release/close system resource or rollback testing data"
if __name__ == '__main__':
unittest.main()
[/pastacode]
如何執行?
可以使用 python
$ python
test_myUnitTest
或是如果有安裝 nose 的話,直接執行 nosetests。nose會搜尋所有 _test的檔名,執行所有 function 包含 test_的測試程式。
$nosetests
Assertion
測試的目的就是希望驗證測試結果的正確性。
因此 unitTest 中,會用到許多的 Assertion 來驗證測試個案的正確性。
之前提到中間斷為測試個案,每一個 function 就是一個測試個案,每一個測試個案建議對應一個 assertion
建議將每一個測試個案獨立而且最小化
Python程式範例
assert 各種不同的用法與範例,可以參考這個程式。
特別說明的是Fail 與 error 是不同的。
Fail 指的是測試結果實際值與預期值比對不同。測試結果失敗。
Error指的是 run-time error.通常是系統或是程式的錯誤所造成。
[pastacode lang=”python” message=”” highlight=”” provider=”manual”]
import unittest
def myfun(a,b):
c = a + b
raise ValueError('invalid args')
class UnitTest_1_test(unittest.TestCase):
def test_pass(self):
self.assertTrue(True,"AssertTue with given True")
# Fail
def test_fail(self):
self.assertTrue(False,"assertTrue with given False")
# runtime error
def test_error(self):
raise RuntimeError('Test error!',"raise runtimeError")
def testEqual(self):
self.assertEqual(2, 2)
def testNotEqual(self):
self.assertEqual(2, 3-2)
def test_assert_raises_myfun(self):
self.assertRaises(ValueError, myfun, 1, 2)
if __name__ == '__main__':
unittest.main()
[/pastacode]
Assertions 常用
Method |
---|
assertTrue(x, msg=None) |
assertFalse(x, msg=None) |
assertIsNone(x, msg=None) |
assertIsNotNone(x, msg=None) |
assertEqual(a, b, msg=None) |
assertNotEqual(a, b, msg=None) |
assertIs(a, b, msg=None) |
assertIsNot(a, b, msg=None) |
assertIn(a, b, msg=None) |
assertNotIn(a, b, msg=None) |
assertIsInstance(a, b, msg=None) |
assertNotIsInstance(a, b, msg=None) |
Other Assertions
Method |
---|
assertAlmostEqual(a, b, places=7, msg=None, delta=None) |
assertNotAlmostEqual(a, b, places=7, msg=None, delta=None) |
assertGreater(a, b, msg=None) |
assertGreaterEqual(a, b, msg=None) |
assertLess(a, b, msg=None) |
assertLessEqual(a, b, msg=None) |
assertRegex(text, regexp, msg=None) |
assertNotRegex(text, regexp, msg=None) |
assertCountEqual(a, b, msg=None) |
assertMultiLineEqual(a, b, msg=None) |
assertSequenceEqual(a, b, msg=None) |
assertListEqual(a, b, msg=None) |
assertTupleEqual(a, b, msg=None) |
assertSetEqual(a, b, msg=None) |
assertDictEqual(a, b, msg=None) |
參考資料