軟件系統(tǒng)的開發(fā)是一個(gè)很復(fù)雜的過程,隨著系統(tǒng)復(fù)雜性的提高,代碼中隱藏的 bug 也可能變得越來越多。為了保證軟件的質(zhì)量,測試是一個(gè)必不可少的部分,甚至還有測試驅(qū)動(dòng)開發(fā)(Test-driven development, TDD)的理念,也就是先測試再編碼。
在計(jì)算機(jī)編程中,單元測試(Unit Testing)又稱為模塊測試,是針對程序模塊(軟件設(shè)計(jì)的最小單位)來進(jìn)行正確性檢驗(yàn)的測試工作,所謂的單元是指一個(gè)函數(shù),一個(gè)模塊,一個(gè)類等。
在 Python 中,我們可以使用 unittest 模塊編寫單元測試。
下面以官方文檔的例子進(jìn)行介紹,該例子對字符串的一些方法進(jìn)行測試:
# -*- coding: utf-8 -*-
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO') # 判斷兩個(gè)值是否相等
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)
在上面,我們定義了一個(gè) TestStringMethods 類,它從 unittest.TestCase 繼承。注意到,我們的方法名都是以 test 開頭,表明該方法是測試方法,不以 test 開頭的方法測試的時(shí)候不會被執(zhí)行。
在方法里面,我們使用了斷言(assert)判斷程序運(yùn)行的結(jié)果是否和預(yù)期相符。其中:
assertEqual 用于判斷兩個(gè)值是否相等;assertTrue/assertFalse 用于判斷表達(dá)式的值是 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)在,讓我們來運(yùn)行上面的單元測試,將上面的代碼保存為文件 mytest.py,通過 -m unittest 參數(shù)運(yùn)行單元測試:
$ python -m unittest mytest
test_isupper (mytest.TestStringMethods) ... ok
test_split (mytest.TestStringMethods) ... ok
test_upper (mytest.TestStringMethods) ... ok
執(zhí)行結(jié)果:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
上面的結(jié)果表明測試通過,我們也可以加 -v 參數(shù)得到更加詳細(xì)的測試結(jié)果:
$ 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
上面這種運(yùn)行單元測試的方法是我們推薦的做法,當(dāng)然,你也可以在代碼的最后添加兩行:
if __name__ == '__main__':
unittest.main()
然后再直接運(yùn)行:
$ python mytest.py
在某些情況下,我們需要在每個(gè)測試方法執(zhí)行前和執(zhí)行后做一些相同的操作,比如我們想在每個(gè)測試方法執(zhí)行前連接數(shù)據(jù)庫,執(zhí)行后斷開數(shù)據(jù)庫連接,為了避免在每個(gè)測試方法中編寫同樣的代碼,我們可以使用 setUp 和 tearDown 方法,比如:
# -*- coding: utf-8 -*-
import unittest
class TestStringMethods(unittest.TestCase):
def setUp(self): # 在每個(gè)測試方法執(zhí)行前被調(diào)用
print 'setUp, Hello'
def tearDown(self): # 在每個(gè)測試方法執(zhí)行后被調(diào)用
print 'tearDown, Bye!'
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO') # 判斷兩個(gè)值是否相等
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í)行結(jié)果:
$ 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 繼承來編寫測試類。