軟件系統(tǒng)的開發(fā)是一個很復雜的過程,隨著系統(tǒng)復雜性的提高,代碼中隱藏的 bug 也可能變得越來越多。為了保證軟件的質量,測試是一個必不可少的部分,甚至還有測試驅動開發(fā)(Test-driven development, TDD)的理念,也就是先測試再編碼。
在計算機編程中,單元測試(Unit Testing)又稱為模塊測試,是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作,所謂的單元是指一個函數(shù),一個模塊,一個類等。
在 Python 中,我們可以使用 unittest
模塊編寫單元測試。
下面以官方文檔的例子進行介紹,該例子對字符串的一些方法進行測試:
# -*- coding: utf-8 -*-
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO') # 判斷兩個值是否相等
def test_isupper(self):
self.assertTrue('FOO'.isupper()) # 判斷值是否為 True
self.assertFalse('Foo'.isupper()) # 判斷值是否為 False
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError): # 檢測異常
s.split(2)
在上面,我們定義了一個 TestStringMethods 類,它從 unittest.TestCase
繼承。注意到,我們的方法名都是以 test
開頭,表明該方法是測試方法,不以 test
開頭的方法測試的時候不會被執(zhí)行。
在方法里面,我們使用了斷言(assert)
判斷程序運行的結果是否和預期相符。其中:
assertEqual
用于判斷兩個值是否相等;assertTrue/assertFalse
用于判斷表達式的值是 True 還是 False;assertRaises
用于檢測異常;斷言方法主要有三種類型:
下面列舉了部分常用的斷言方法:
Method | Checks that |
---|---|
assertEqual(a, b) | a == b |
assertNotEqual(a, b) | a != b |
assertGreater(a, b) | a > b |
assertGreaterEqual(a, b) | a >= b |
assertLess(a, b) | a < b |
assertLessEqual(a, b) | a <= b |
assertTrue(x) | bool(x) is True |
assertFalse(x) | bool(x) is False |
assertIs(a, b) | a is b |
assertIsNot(a, b) | a is not b |
assertIsNone(x) | x is None |
assertIsNotNone(x) | x is not None |
assertIn(a, b) | a in b |
assertNotIn(a, b) | a not in b |
assertIsInstance(a, b) | isinstance(a, b) |
assertNotIsInstance(a, b) | not isinstance(a, b) |
現(xiàn)在,讓我們來運行上面的單元測試,將上面的代碼保存為文件 mytest.py
,通過 -m unittest
參數(shù)運行單元測試:
$ python -m unittest mytest
test_isupper (mytest.TestStringMethods) ... ok
test_split (mytest.TestStringMethods) ... ok
test_upper (mytest.TestStringMethods) ... ok
執(zhí)行結果:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
上面的結果表明測試通過,我們也可以加 -v
參數(shù)得到更加詳細的測試結果:
$ python -m unittest -v mytest
test_isupper (mytest.TestStringMethods) ... ok
test_split (mytest.TestStringMethods) ... ok
test_upper (mytest.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
上面這種運行單元測試的方法是我們推薦的做法,當然,你也可以在代碼的最后添加兩行:
if __name__ == '__main__':
unittest.main()
然后再直接運行:
$ python mytest.py
在某些情況下,我們需要在每個測試方法執(zhí)行前和執(zhí)行后做一些相同的操作,比如我們想在每個測試方法執(zhí)行前連接數(shù)據(jù)庫,執(zhí)行后斷開數(shù)據(jù)庫連接,為了避免在每個測試方法中編寫同樣的代碼,我們可以使用 setUp 和 tearDown 方法,比如:
# -*- coding: utf-8 -*-
import unittest
class TestStringMethods(unittest.TestCase):
def setUp(self): # 在每個測試方法執(zhí)行前被調用
print 'setUp, Hello'
def tearDown(self): # 在每個測試方法執(zhí)行后被調用
print 'tearDown, Bye!'
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO') # 判斷兩個值是否相等
def test_isupper(self):
self.assertTrue('FOO'.isupper()) # 判斷值是否為 True
self.assertFalse('Foo'.isupper()) # 判斷值是否為 False
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError): # 檢測異常
s.split(2)
看看執(zhí)行結果:
$ python -m unittest -v mytest
test_isupper (mytest.TestStringMethods) ... setUp, Hello
tearDown, Bye!
ok
test_split (mytest.TestStringMethods) ... setUp, Hello
tearDown, Bye!
ok
test_upper (mytest.TestStringMethods) ... setUp, Hello
tearDown, Bye!
ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
unittest.TestCase
繼承來編寫測試類。