unittest作為一個(gè)python中的基本模塊,是其他框架和工具的基礎(chǔ),官方文檔神馬的最實(shí)用了:https://docs.python.org/2/library/unittest.html
對(duì)官方文檔做了一個(gè)粗略的翻譯,稍有調(diào)整。
python版本:2.7
基本概念
- test fixture
一個(gè)test fixture代表執(zhí)行一個(gè)或者多個(gè)測(cè)試時(shí)需要準(zhǔn)備環(huán)境,以及相關(guān)聯(lián)的清理環(huán)境的工作。這包含很多內(nèi)容,比如創(chuàng)建臨時(shí)的數(shù)據(jù)庫(kù)、目錄等。 - test case
一個(gè)test case就是測(cè)試用例,測(cè)試當(dāng)中的最小單元。unittest提供一個(gè)基本的類TestCase,用來(lái)創(chuàng)建一個(gè)test case。 - test suite
test suite是一組test case或者test suite的集合,也可以兩者都有。用來(lái)將需要一同執(zhí)行的測(cè)試用例聚合到一起。 - test runner
一個(gè)test runner是用來(lái)執(zhí)行測(cè)試用例的,對(duì)測(cè)試進(jìn)行編排并把結(jié)果返回給用戶。
一個(gè)TestCase的實(shí)例應(yīng)該是完全獨(dú)立的,可以獨(dú)立的運(yùn)行測(cè)試,也可以和其他測(cè)試用例進(jìn)行組合。最簡(jiǎn)單的TestCase子類簡(jiǎn)單的重寫runTest()方法執(zhí)行特定代碼就可以:
import unittest
class JustForTest(unittest.TestCase):
def runTest(self):
length = 10
self.assertEqual(10, length)
通過(guò)unittest模塊可以在命令行下對(duì)一個(gè)模塊、一個(gè)類或者是一個(gè)方法執(zhí)行測(cè)試,比如對(duì)這個(gè)test.py模塊執(zhí)行單元測(cè)試:
? python -m unittest test
.
---------------------------------------------------------
Ran 1 test in 0.000s
OK
#
一個(gè)簡(jiǎn)單用例
unittest模塊為構(gòu)建和執(zhí)行測(cè)試提供了非常豐富的工具集,下面這個(gè)例子用來(lái)測(cè)試三個(gè)字符串的方法:
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
with self.assertRaises(TypeError):
s.split(2)
if __name__ == '__main__':
unittest.main()
測(cè)試用例是unittest.TestCase的子類,三個(gè)獨(dú)立的測(cè)試是以test開(kāi)頭的。用這樣的命名規(guī)則來(lái)約定哪些方法是test runner需要執(zhí)行的。
每個(gè)測(cè)試的關(guān)鍵在于調(diào)用assertEqual()進(jìn)行檢查期望的結(jié)果;assertTrue()和assertFalse()用來(lái)判斷一個(gè)條件;assertRaises()用來(lái)確認(rèn)拋出了一個(gè)指定的異常。這些方法用來(lái)代替assert語(yǔ)句,test runner可以收集所有的結(jié)果并生成最后的報(bào)告。
代碼總13~14行是一個(gè)簡(jiǎn)單的用來(lái)執(zhí)行測(cè)試的方式。unittest.main()給測(cè)試腳本提供了一個(gè)命令行接口。當(dāng)從命令行執(zhí)行的時(shí)候,上面的腳本會(huì)有如下輸出:
? python basic_example.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
不使用unittest.main()的話也可以用其他方式替代,比如13、14行可以替換為:
#if __name__ == '__main__':
# unittest.main()
suite = unittest.TestLoader().loadTestsFromTestCase(TestStringMethods)
unittest.TextTestRunner(verbosity=2).run(suite)
可以得到更加清晰的輸出:
? python_unit_test_study python basic_example.py
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
命令行接口
unittest模塊可以從命令行執(zhí)行模塊中的測(cè)試,可以是一個(gè)類也可以使單獨(dú)的測(cè)試用例:
? python -m unittest basic_example.TestStringMethod$
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
? python_unit_test_study python -m unittest basic_example.TestStringMethods
.test_isupper
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
也可以傳遞一個(gè)模塊列表,執(zhí)行多個(gè)模塊中的測(cè)試用例:
? python -m unittest test_module1 test_module2
-v參數(shù)可以獲得更多的內(nèi)容:
? python -m unittest -v basic_example
test_isupper (basic_example.TestStringMethods) ... ok
test_split (basic_example.TestStringMethods) ... ok
test_upper (basic_example.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
-h可以獲得完整命令行參數(shù)的幫助說(shuō)明:
? python -m unittest -h
Usage: python -m unittest [options] [tests]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
-f, --failfast Stop on first failure
-c, --catch Catch control-C and display results
-b, --buffer Buffer stdout and stderr during test runs
Examples:
python -m unittest test_module - run tests from test_module
python -m unittest module.TestClass - run tests from module.TestClass
python -m unittest module.Class.test_method - run specified test method
[tests] can be a list of any number of test modules, classes and test
methods.
Alternative Usage: python -m unittest discover [options]
Options:
-v, --verbose Verbose output
-f, --failfast Stop on first failure
-c, --catch Catch control-C and display results
-b, --buffer Buffer stdout and stderr during test runs
-s directory Directory to start discovery ('.' default)
-p pattern Pattern to match test files ('test*.py' default)
-t directory Top level directory of project (default to
start directory)
For test discovery all test modules must be importable from the top
level directory of the project.
測(cè)試發(fā)現(xiàn)
unittest支持非常簡(jiǎn)單的測(cè)試發(fā)現(xiàn)。為了兼容測(cè)試發(fā)現(xiàn),所有的測(cè)試文件(test_xxx.py)必須是可從項(xiàng)目的頂級(jí)目錄導(dǎo)入的模塊或包。測(cè)試發(fā)現(xiàn)由TestLoader.discover()實(shí)現(xiàn),但是可以通過(guò)命令行使用,基本的用法如下:
? python -m unittest discover
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s
OK
? python_unit_test_study python -m unittest discover -h
Usage: python -m unittest discover [options]
Options:
-h, --help show this help message and exit
-v, --verbose Verbose output
-f, --failfast Stop on first fail or error
-c, --catch Catch Ctrl-C and display results so far
-b, --buffer Buffer stdout and stderr during tests
-s START, --start-directory=START
Directory to start discovery ('.' default)
-p PATTERN, --pattern=PATTERN
Pattern to match tests ('test*.py' default)
-t TOP, --top-level-directory=TOP
Top level directory of project (defaults to start
directory)
組織代碼
單元測(cè)試最基本的結(jié)構(gòu)應(yīng)該是測(cè)試用例——必須設(shè)置并檢查正確性的單獨(dú)場(chǎng)景。在unittest中,測(cè)試用例就是unittest的TestCase類的一個(gè)實(shí)例。寫測(cè)試代碼時(shí),必須書寫TestCase的子類,或者使用FunctionTestCase。
文章開(kāi)頭講過(guò),最基本的TestCase子類就是重寫runTest()方法即可,在runTest()中執(zhí)行響應(yīng)的測(cè)試代碼。比如:
import unittest
class DefaultWidgetSizeTestCase(unittest.TestCase):
def runTest(self):
widget = Widget('The widget')
self.assertEqual(widget.size(), (50, 50), 'incorrect default size')
為了執(zhí)行測(cè)試內(nèi)容,使用TestCase基類提供的assert*()中的方法來(lái)檢查結(jié)果。如果測(cè)試失敗,就會(huì)拋出異常,unittest會(huì)將這個(gè)測(cè)試標(biāo)記為Failure。而其他的異常都會(huì)被當(dāng)做Error處理。這可以幫助我們判斷代碼中的問(wèn)題:failure是由于測(cè)試結(jié)果引起的錯(cuò)誤-期望值是5得到的卻是6。而Errors是由于代碼本身的錯(cuò)誤引起,比如常見(jiàn)的TypeError等。
當(dāng)我們對(duì)同一類型的測(cè)試內(nèi)容寫測(cè)試代碼時(shí),很多測(cè)試方法的構(gòu)建和初始化可能是重復(fù)的,這種情況下我們可以使用setUp()方法,setUp()可以將初始化統(tǒng)一抽離出來(lái),在一個(gè)測(cè)試方法執(zhí)行前都會(huì)先執(zhí)行,比如:
如果setUp()方法拋出異常的話,測(cè)試框架認(rèn)為測(cè)試過(guò)程遇到了錯(cuò)誤,runTest()方法不會(huì)被執(zhí)行的。
import unittest
class SimpleWidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget("The widget")
class DefaultWidgetSizeTestCase(SimpleWidgetTestCase):
def runTest(self):
self.assertEqual(self.widget.size(), (50, 50),
'incorrect default size')
class WidgetResizeTestCase(SimpleWidgetTestCase):
def runTest(self):
self.widget.resize(100, 150)
self.assertEqual(self.widget.size(), (100, 150),
'wrong size after resize')
執(zhí)行結(jié)果:
? python -m unittest test_widget
EE
======================================================================
ERROR: runTest (test_widget.DefaultWidgetSizeTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_widget.py", line 5, in setUp
self.widget = Widget("The widget")
NameError: global name 'Widget' is not defined
======================================================================
ERROR: runTest (test_widget.WidgetResizeTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_widget.py", line 5, in setUp
self.widget = Widget("The widget")
NameError: global name 'Widget' is not defined
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (errors=2)
同樣的,我們可以使用tearDown()方法在runTest()執(zhí)行結(jié)束后進(jìn)行清理,如果setUp()方法執(zhí)行成功,不論runTest()是否成功,tearDown()都會(huì)執(zhí)行。
這樣一個(gè)初始化、清理的完整的測(cè)試環(huán)境叫做fixture,通常很多小的測(cè)試使用的是相同的fixture的,
import unittest
class WidgetTestCase(unittest.TestCase):
def setUp(self):
self.widget = Widget('The widget')
def tearDown(self):
self.widget.dispose()
self.widget = None
def test_default_size(self):
self.assertEqual(self.widget.size(), (50, 50),
'incorrect default size')
def test_resize(self):
self.widget.resize(100, 150)
self.assertEqual(self.widget.size(), (100, 150),
'wrong size after resize')
這里沒(méi)有使用runTest()方法,但是使用了其他兩個(gè)測(cè)試方法作為替代。測(cè)試類實(shí)例會(huì)執(zhí)行每一個(gè)test_*()方法,self.widget會(huì)對(duì)每一個(gè)實(shí)例進(jìn)行創(chuàng)建和銷毀。這種構(gòu)建方法,創(chuàng)建實(shí)例的時(shí)候我們需要具體的指出需要執(zhí)行的測(cè)試方法:
defaultSizeTestCase = WidgetTestCase('test_default_size')
resizeTestCase = WidgetTestCase('test_resize')
unittest提供test suite(測(cè)試套件)可以將測(cè)試用例的實(shí)例很好的按照功能特性進(jìn)行合理的組織,在unittest中通過(guò)TestSuite類實(shí)現(xiàn):
widgetTestSuite = unittest.TestSuite()
widgetTestSuite.addTest(WidgetTestCase('test_default_size'))
widgetTestSuite.addTest(WidgetTestCase('test_resize'))
為了方便測(cè)試,在每一個(gè)模塊中提供一個(gè)可調(diào)用的預(yù)構(gòu)建的test suite是一個(gè)不錯(cuò)的主意:
def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase('test_default_size'))
suite.addTest(WidgetTestCase('test_resize'))
return suite
也可以這樣寫:
def suite():
tests = ['test_default_size', 'test_resize']
return unittest.TestSuite(map(WidgetTestCase, tests))
由于使用相似名字的測(cè)試函數(shù)來(lái)創(chuàng)建一個(gè)TestCase子類是非常通用的模式,unittest提供了一個(gè)TestLoader類可以自動(dòng)的創(chuàng)建測(cè)試套件并用獨(dú)立的測(cè)試進(jìn)行填充,比如:
suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase)
這就創(chuàng)建了一個(gè)測(cè)試套件,將會(huì)執(zhí)行WidgetTestCase.test_default_size()和WidgetTestCase.test_resize。TestLoader將會(huì)自動(dòng)的識(shí)別以test_開(kāi)頭的測(cè)試方法。
各個(gè)測(cè)試的執(zhí)行順序是由測(cè)試的函數(shù)名按照字符串內(nèi)建順序執(zhí)行的。
測(cè)試套件本身也可以像測(cè)試用例一樣組織起來(lái):
suite1 = module1.TheTestSuite()
suite2 = module2.TheTestSuite()
alltests = unittest.TestSuite([suite1, suite2])
我們可以將測(cè)試用例與測(cè)試套件的定義放在與測(cè)試代碼相同的模塊中,但將測(cè)試代碼放在單獨(dú)的模塊中又幾個(gè)好處:
- 測(cè)試模塊可以獨(dú)立的通過(guò)命令行進(jìn)行執(zhí)行
- 測(cè)試代碼可以更容易的與發(fā)布代碼分離
- 沒(méi)有必要的情況下不必為了適應(yīng)被測(cè)試代碼而頻繁更改測(cè)試代碼
- 測(cè)試的代碼更容易重構(gòu)
。。。等等
跳過(guò)測(cè)試以及異常測(cè)試
unittest支持跳過(guò)單獨(dú)的測(cè)試方法甚至是整個(gè)測(cè)試類。也支持將一個(gè)測(cè)試標(biāo)記為“expected failure”。
跳過(guò)一個(gè)測(cè)試可以使用skip()描述符或者它的一個(gè)條件語(yǔ)句,最基本的skip使用像這樣:
import unittest
import sys
class MyTestCase(unittest.TestCase):
@unittest.skip('demonstrating sipping')
def test_nothing(self):
self.fail("shouldn't happen")
@unittest.skipUnless(sys.platform.startswith("win"), 'requires Windows')
def test_windows_support(self):
pass
執(zhí)行結(jié)果:
? python_unit_test_study python -m unittest -v mylib
test_nothing (mylib.MyTestCase) ... skipped 'demonstrating sipping'
test_windows_support (mylib.MyTestCase) ... skipped 'requires Windows'
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK (skipped=2)
如果想跳過(guò)整個(gè)測(cè)試類,方法是和上面是一樣的。
期望失敗通過(guò)expectedFailure()描述符實(shí)現(xiàn)的:
@unittest.expectedFailure
def test_format(self):
pass
執(zhí)行結(jié)果:
? python -m unittest -v mylib
test_format (mylib.MyTestCase) ... unexpected success
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK (unexpected successes=1)
被跳過(guò)的測(cè)試不會(huì)執(zhí)行setUp和tearDown,被跳過(guò)的類也不會(huì)執(zhí)行setUpClass和tearDownClass。