版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請標明原文地址,謝謝 ^_^ https://blog.csdn.net/xiaoquantouer/article/details/75089200
unittest 官方文檔:https://docs.python.org/3/library/unittest.html
unittest 中文文檔:https://blog.csdn.net/ljl6158999/article/details/80994979
一、什么是單元測試
單元測試是用來對一個模塊、一個函數(shù)或者一個類來進行正確性檢驗的測試工作。
比如對于函數(shù)abs(),我們可以編寫的測試用例為:
(1)輸入正數(shù),比如1、1.2、0.99,期待返回值與輸入相同
(2)輸入復(fù)數(shù),比如-1、-1.2、-0.99,期待返回值與輸入相反
(3)輸入0,期待返回0
(4)輸入非數(shù)值類型,比如None、[]、{}、期待拋出TypeError
把上面這些測試用例放到一個測試模塊里,就是一個完整的單元測試
二、unittest工作原理
unittest中最核心的四部分是:TestCase,TestSuite,TestRunner,TestFixture
(1)一個TestCase的實例就是一個測試用例。測試用例就是指一個完整的測試流程,包括測試前準備環(huán)境的搭建(setUp),執(zhí)行測試代碼(run),以及測試后環(huán)境的還原(tearDown)。元測試(unit test)的本質(zhì)也就在這里,一個測試用例是一個完整的測試單元,通過運行這個測試單元,可以對某一個問題進行驗證。
(2)而多個測試用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。
(3)TestLoader是用來加載TestCase到TestSuite中的。
(4)TextTestRunner是來執(zhí)行測試用例的,其中的run(test)會執(zhí)行TestSuite/TestCase中的run(result)方法
(5)測試的結(jié)果會保存到TextTestResult實例中,包括運行了多少測試用例,成功了多少,失敗了多少等信息。
綜上,整個流程就是首先要寫好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來運行TestSuite,運行的結(jié)果保存在TextTestResult中,整個過程集成在unittest.main模塊中。
三、下面舉兩個實例,來看看unittest如何測試一個簡單的函數(shù)
(1)編寫一個Dict類,這個類的行為和dict一致,但是可以通過屬性來訪問例如
>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d.a
1
mydict.py代碼如下:
classDict(dict):
def__init__(self, **kw):
? ? ? ? super(Dict, self).__init__(**kw)
def__getattr__(self, key):
try:
returnself[key]
exceptKeyError:
raiseAttributeError(r"'Dict' object has no attribute '%s'"% key)
def__setattr__(self, key, value):
? ? ? ? self[key] = value
用于測試的文件mydict_test.py代碼如下:
importunittest
frommydictimportDict
classTestDict(unittest.TestCase):
deftest_init(self):
d = Dict(a=1, b='test')
self.assertEqual(d.a,1)# 判斷d.a是否等于1
self.assertEqual(d.b,'test')# 判斷d.b是否等于test
self.assertTrue(isinstance(d, dict))# 判斷d是否是dict類型
deftest_key(self):
? ? ? ? d = Dict()
d['key'] ='value'
self.assertEqual(d.key,'value')
deftest_attr(self):
? ? ? ? d = Dict()
d.key ='value'
self.assertTrue('key'ind)
self.assertEqual(d['key'],'value')
deftest_keyerror(self):
? ? ? ? d = Dict()
withself.assertRaises(KeyError):# 通過d['empty']訪問不存在的key時,斷言會拋出keyerror
value = d['empty']
deftest_attrerror(self):
? ? ? ? d = Dict()
withself.assertRaises(AttributeError):# 通過d.empty訪問不存在的key時,我們期待拋出AttributeError
? ? ? ? ? ? value = d.empty
if__name__ =='__main__':
? ? unittest.main()
直接把mydict_test.py當(dāng)普通的Python腳本運行即可
輸出:
.....
----------------------------------------------------------------------
Ran5testsin0.000s
OK
(2)測一個簡單的加減乘除接口
mathfunc.py文件代碼如下:
defadd(a, b):
returna + b
defminus(a, b):
returna - b
defmulti(a, b):
returna * b
defdivide(a, b):
returna / b
test_mathfunc.py文件代碼如下:
importunittest
frommathfuncimport*
classTestMathFunc(unittest.TestCase):
deftest_add(self):
self.assertEqual(3, add(1,2))
self.assertNotEqual(3, add(2,2))
deftest_minus(self):
self.assertEqual(1, minus(3,2))
deftest_multi(self):
self.assertEqual(6, multi(3,2))
deftest_divide(self):
self.assertEqual(2, divide(6,3))
self.assertEqual(2.5, divide(5,2))
if__name__ =='__main__':
unittest.main()
輸出:
.F..
======================================================================
FAIL: test_divide (__main__.TestDict)
----------------------------------------------------------------------
Traceback (most recent call last):
File"D:/pythonWorkspace/test_mathfunc.py", line20,intest_divide
self.assertEqual(2.5, divide(5,2))
AssertionError:2.5!=2
----------------------------------------------------------------------
Ran4testsin0.000s
FAILED (failures=1)
可以看到一共運行了4個測試,失敗了1個,并且給出了失敗原因,2.5!=2,也就是說我們的divide方法是有問題的。
關(guān)于輸出的幾點說明:
1、在第一行給出了每一個用例執(zhí)行的結(jié)果的標識,成功是.,失敗是F,出錯是E,跳過是S。從上面可以看出,測試的執(zhí)行跟方法的順序沒有關(guān)系,divide方法寫在了第4個,但是卻在第2個執(zhí)行。
2、每個測試方法均以test開頭,否則不能被unittest識別
3、在uniitest.main()中加verbosity參數(shù)可以控制輸出的錯誤報告的詳細程度,默認是1,如果設(shè)為0, 則不輸出每一用例的執(zhí)行結(jié)果,即沒有上面的結(jié)果中的第1行,如果設(shè)為2,則輸出詳細的執(zhí)行結(jié)果,如下所示:
test_add (__main__.TestMathFunc) ... ok
test_divide (__main__.TestMathFunc) ... FAIL
test_minus (__main__.TestMathFunc) ... ok
test_multi (__main__.TestMathFunc) ... ok
======================================================================
FAIL: test_divide (__main__.TestMathFunc)
----------------------------------------------------------------------
Traceback (most recent call last):
File"D:/pythonWorkspace/test_mathfunc.py", line20,intest_divide
self.assertEqual(2.5, divide(5,2))
AssertionError:2.5!=2
----------------------------------------------------------------------
Ran4testsin0.000s
FAILED (failures=1)
四、組織TestSuite
上面的測試用例在執(zhí)行的時候沒有按照順序執(zhí)行,如果想要讓用例按照你設(shè)置的順序執(zhí)行就用到了TestSuite。我們添加到TestSuite中的case是會按照添加的順序執(zhí)行的。
現(xiàn)在我們只有一個測試文件,如果有多個測試文件,也可以用TestSuite組織起來。
繼續(xù)上面第二加減乘除的例子,現(xiàn)在再新建一個文件,test_suite.py,代碼如下:
# coding=utf-8
importunittest
fromtest_mathfuncimportTestMathFunc
if__name__ =='__main__':
? ? suite = unittest.TestSuite()
tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
? ? suite.addTests(tests)
runner = unittest.TextTestRunner(verbosity=2)
? ? runner.run(suite)
執(zhí)行結(jié)果如下:
test_add (test_mathfunc.TestMathFunc) ... ok
test_minus (test_mathfunc.TestMathFunc) ... ok
test_divide (test_mathfunc.TestMathFunc) ... FAIL
======================================================================
FAIL: test_divide (test_mathfunc.TestMathFunc)
----------------------------------------------------------------------
Traceback (most recent call last):
File"D:\pythonWorkspace\HTMLTest\test_mathfunc.py", line20,intest_divide
self.assertEqual(2.5, divide(5,2))
AssertionError:2.5!=2
----------------------------------------------------------------------
Ran3testsin0.000s
FAILED (failures=1)
五、將結(jié)果輸出到文件
現(xiàn)在我們的測試結(jié)果只能輸出到控制臺,現(xiàn)在我們想將結(jié)果輸出到文件中以便后續(xù)可以查看。
將test_suite.py進行一點修改,代碼如下:
# coding=utf-8
importunittest
fromtest_mathfuncimportTestMathFunc
if__name__ =='__main__':
? ? suite = unittest.TestSuite()
tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
? ? suite.addTests(tests)
withopen('UnittestTextReport.txt','a')asf:
runner = unittest.TextTestRunner(stream=f, verbosity=2)
? ? ? ? runner.run(suite)
運行該文件,就會發(fā)現(xiàn)目錄下生成了'UnittestTextReport.txt,所有的執(zhí)行報告均輸出到了此文件中。
六、test fixture的setUp和tearDown
當(dāng)遇到要啟動一個數(shù)據(jù)庫這種情況時,只想在開始時連接上數(shù)據(jù)庫,在結(jié)束時關(guān)閉連接。那么可以使用setUp和tearDown函數(shù)。
classTestDict(unittest.TestCase):
defsetUp(self):
print'setUp...'
deftearDown(self):
print'tearDown...'
這兩個方法在每個測試方法執(zhí)行前以及執(zhí)行后執(zhí)行一次,setUp用來為測試準備環(huán)境,tearDown用來清理環(huán)境,以備后續(xù)的測試。
如果想要在所有case執(zhí)行之前準備一次環(huán)境,并在所有case執(zhí)行結(jié)束之后再清理環(huán)境,我們可以用setUpClass()與tearDownClass(),代碼格式如下:
classTestMathFunc(unittest.TestCase):
? ? @classmethod
defsetUpClass(cls):
print"setUp"
? ? @classmethod
deftearDownClass(cls):
print"tearDown"
七、跳過某個case
unittest提供了幾種方法可以跳過case
(1)skip裝飾器
代碼如下
# coding=utf-8
importunittest
frommathfuncimport*
classTestMathFunc(unittest.TestCase):
? ? .....
? ? @unittest.skip("i don't want to run this case.")
deftest_minus(self):
self.assertEqual(1, minus(3,2))
輸出:
test_add (test_mathfunc.TestMathFunc) ... ok
test_minus (test_mathfunc.TestMathFunc) ... skipped"i don't want to run this case."
test_divide (test_mathfunc.TestMathFunc) ... FAIL
======================================================================
FAIL: test_divide (test_mathfunc.TestMathFunc)
----------------------------------------------------------------------
Traceback (most recent call last):
File"D:\pythonWorkspace\HTMLTest\test_mathfunc.py", line28,intest_divide
self.assertEqual(2.5, divide(5,2))
AssertionError:2.5!=2
----------------------------------------------------------------------
Ran3testsin0.000s
FAILED (failures=1, skipped=1)
skip裝飾器一共有三個
unittest,skip(reason):無條件跳過
unittest.skipIf(condition, reason):當(dāng)condition為True時跳過
unittest.skipUnless(condition, reason):當(dāng)condition為False時跳過
(2)TestCase.skipTest()方法
classTestMathFunc(unittest.TestCase):
...
deftest_minus(self):
self.skipTest('do not run this.')
self.assertEqual(1, minus(3,2))
輸出:
test_add (test_mathfunc.TestMathFunc) ... ok
test_minus (test_mathfunc.TestMathFunc) ... skipped'do not run this.'
test_divide (test_mathfunc.TestMathFunc) ... FAIL
======================================================================
FAIL: test_divide (test_mathfunc.TestMathFunc)
----------------------------------------------------------------------
Traceback (most recent call last):
File"D:\pythonWorkspace\HTMLTest\test_mathfunc.py", line20,intest_divide
self.assertEqual(2.5, divide(5,2))
AssertionError:2.5!=2
----------------------------------------------------------------------
Ran3testsin0.000s
FAILED (failures=1, skipped=1)
八、用HTMLTestRunner輸出漂亮的HTML報告
txt格式的文本執(zhí)行報告過于簡陋,這里我們學(xué)習(xí)一下借助HTMLTestRunner生成HTML報告。首先需要下載HTMLTestRunner.py,并放到當(dāng)前目錄下,或者python目錄下的Lib中,就可以導(dǎo)入運行了。
下載地址:http://tungwaiyip.info/software/HTMLTestRunner.html
將test_suite.py代碼修改如下:
# coding=utf-8
importunittest
fromtest_mathfuncimportTestMathFunc
fromHTMLTestRunnerimportHTMLTestRunner
if__name__ =='__main__':
? ? suite = unittest.TestSuite()
tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
? ? suite.addTests(tests)
withopen('HTMLReport.html','w')asf:
? ? ? ? runner = HTMLTestRunner(stream=f,
title ='MathFunc Test Report',
description='generated by HTMLTestRunner.',
verbosity=2
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? )
? ? ? ? runner.run(suite)
執(zhí)行后,控制臺輸出如下:
ok test_add (test_mathfunc.TestMathFunc)
F? test_divide (test_mathfunc.TestMathFunc)
Time Elapsed:0:00:00.001000
生成的html:
九、總結(jié)
1、unittest是python自帶的單元測試框架,我們可以用其來作為我們自動化測試框架的用例組織執(zhí)行框架。
2、unittest的流程:寫好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來運行TestSuite,運行的結(jié)果保存在TextTestResult中,我們通過命令行或者unittest.main()執(zhí)行時,main會調(diào)用TextTestRunner中的run來執(zhí)行,或者我們可以直接通過TextTestRunner來執(zhí)行用例。
3、一個class繼承unittest.TestCase即是一個TestCase,其中以 test 開頭的方法在load時被加載為一個真正的TestCase。
4、verbosity參數(shù)可以控制執(zhí)行結(jié)果的輸出,0 是簡單報告、1 是一般報告、2 是詳細報告。
5、可以通過addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。
6、用 setUp()、tearDown()、setUpClass()以及 tearDownClass()可以在用例執(zhí)行前布置環(huán)境,以及在用例執(zhí)行后清理環(huán)境
7、我們可以通過skip,skipIf,skipUnless裝飾器跳過某個case,或者用TestCase.skipTest方法。
8、參數(shù)中加stream,可以將報告輸出到文件:可以用TextTestRunner輸出txt報告,以及可以用HTMLTestRunner輸出html報告。