單元測(cè)試的定義
1. 什么是單元測(cè)試?
? 單元測(cè)試是指,對(duì)軟件中的最小可測(cè)試單元在與程序其他部分相隔離的情況下進(jìn)行檢查和驗(yàn)證的工作,這里的最小可測(cè)試單元通常是指函數(shù)或者類,一般是開發(fā)來做的,按照測(cè)試階段來分,就是單元測(cè)試、集成測(cè)試、系統(tǒng)測(cè)試以及驗(yàn)收測(cè)試。
2.為什么要做單元測(cè)試?
- ?? 單元測(cè)試之后,才是集成測(cè)試,單個(gè)單個(gè)的功能模塊測(cè)試通過之后,才能把單個(gè)功能模塊集成起來做集成測(cè)試,為了從底層發(fā)現(xiàn)bug,單元測(cè)試時(shí)可以減少合成后出現(xiàn)的問題。
- ?? 越早發(fā)現(xiàn)bug越好,這樣可以早點(diǎn)發(fā)現(xiàn)問題,不然問題累計(jì)到后面,很可能會(huì)因?yàn)橐粋€(gè)做錯(cuò)了而導(dǎo)致整個(gè)模塊甚至更大范圍的推倒重來,對(duì)于時(shí)間和經(jīng)費(fèi)來說,是非常浪費(fèi)的!
- ?? 對(duì)于測(cè)試來說,單元測(cè)試就是為了執(zhí)行用例,輸入測(cè)試數(shù)據(jù)--》輸出測(cè)試結(jié)果
unittest框架及原理
? 做過自動(dòng)化測(cè)試的同學(xué)應(yīng)該都知道python中的unittest框架,它是python自帶的一套測(cè)試框架,學(xué)習(xí)起來也相對(duì)較容易,unittest框架最核心的四個(gè)概念:
? ?? test case:就是我們的測(cè)試用例,unittest中提供了一個(gè)基本類TestCase,可以用來創(chuàng)建新的測(cè)試用例,一個(gè)TestCase的實(shí)例就是一個(gè)測(cè)試用例;unittest中測(cè)試用例方法都是以test開頭的,且執(zhí)行順序會(huì)按照方法名的ASCII值排序。
? ?? test fixure:測(cè)試夾具,用于測(cè)試用例環(huán)境的搭建和銷毀。即用例測(cè)試前準(zhǔn)備環(huán)境的搭建(SetUp前置條件),測(cè)試后環(huán)境的還原(TearDown后置條件),比如測(cè)試前需要登錄獲取token等就是測(cè)試用例需要的環(huán)境,運(yùn)行完后執(zhí)行下一個(gè)用例前需要還原環(huán)境,以免影響下一條用例的測(cè)試結(jié)果。
? ?? test suite:測(cè)試套件,用來把需要一起執(zhí)行的測(cè)試用例集中放到一塊執(zhí)行,相當(dāng)于一個(gè)籃子。我們可以使用TestLoader來加載測(cè)試用例到測(cè)試套件中。
? ?? test runner:用來執(zhí)行測(cè)試用例的,并返回測(cè)試用例的執(zhí)行結(jié)果。它還可以用圖形或者文本接口,把返回的測(cè)試結(jié)果更形象的展現(xiàn)出來,如:HTMLTestRunner。
unittest的斷言
? 在python基礎(chǔ)中,我們有講過一個(gè)assert斷言,使用方法比較簡單,即assert 表達(dá)式, 提示信息
,而unittest框架中也提供了一個(gè)自帶的斷言方式,主要有以下幾種:
方法 | 檢查 |
---|---|
assertEqual(a, b,msg=None) | a ==b |
assertNotEqual(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) |
? 如果斷言失敗即不通過就會(huì)拋出一個(gè)AssertionError
斷言錯(cuò)誤,成功則標(biāo)識(shí)為通過,以上幾種方式都有一個(gè)共同點(diǎn),就是都有一個(gè)msg參數(shù)(表中只列了一個(gè),其實(shí)都有),默認(rèn)是None,即msg = None
,如果指定msg參數(shù)的值,則將該信息作為失敗的錯(cuò)誤信息返回。
TestCase測(cè)試用例
編寫測(cè)試用例前,我們需要建一個(gè)測(cè)試類繼承unittest里面的TestCase類,繼承這個(gè)類之后我們才是真正的使用unittest框架去寫測(cè)試用例,編寫測(cè)試用例的步驟如下:
- 導(dǎo)入unittest模塊
- 創(chuàng)建一個(gè)測(cè)試類,并繼承
unittest.TestCase()
- 定義測(cè)試方法,方法名必須以test_開頭
- 調(diào)用
unittest.main()
方法來運(yùn)行測(cè)試用例,unittest.main()方法會(huì)搜索該模塊下所有以test開頭的測(cè)試用例方法,并自動(dòng)執(zhí)行
下面以注冊(cè)功能為例,這個(gè)register.py就是注冊(cè)功能的代碼,沒有前端界面,功能比較簡單,只是方便用于演示,直接導(dǎo)入就可以使用。
python
# register.py
users = [{'username': 'test', 'password': '123456'}]
def register(username, password1, password2):
if not all([username, password1, password2]):
return {"code": 0, "msg": "所有參數(shù)不能為空"}
# 注冊(cè)功能
for user in users:
if username == user['username']:
return {"code": 0, "msg": "該用戶名已存在!"}
else:
if password1 != password2:
return {"code": 0, "msg": "兩次密碼輸入不一致!"}
else:
if 6 <= len(username) >= 6 and 6 <= len(password1) <= 18:
users.append({'username': username, 'password': password2})
return {"code": 1, "msg": "注冊(cè)成功"}
else:
return {"code": 0, "msg": "用戶名和密碼必須在6-18位之間"}
? 下面是編寫測(cè)試用例例子:
python
# test_register.py
import unittest
from register import register # 導(dǎo)入被測(cè)試的代碼
class TestRegister(unittest.TestCase):
"""注冊(cè)接口測(cè)試用例類"""
def test_register_success(self):
"""注冊(cè)成功"""
data = ("mikitest", "miki123", "miki123") # 測(cè)試數(shù)據(jù)
expected = {"code": 1, "msg": "注冊(cè)成功"} # 預(yù)期結(jié)果
result = register(*data) # 把測(cè)試數(shù)據(jù)傳到被測(cè)的代碼,接收實(shí)際結(jié)果
self.assertEqual(expected, result) # 斷言,預(yù)期和實(shí)際是否一致,一致即用例通過
def test_username_isnull(self):
"""注冊(cè)失敗-用戶名為空"""
data = ("", "miki123", "miki123")
expected = {"code": 0, "msg": "所有參數(shù)不能為空"}
result = register(*data)
self.assertEqual(expected, result)
def test_username_lt6(self):
"""注冊(cè)失敗-用戶名大于18位"""
data = ("mikitestmikitestmikitest", "miki123", "miki123")
expected = {"code": 0, "msg": "用戶名和密碼必須在6-18位之間!"}
result = register(*data)
self.assertEqual(expected, result) # 這條用例應(yīng)該是不通過的,注冊(cè)代碼bug
def test_pwd1_not_pwd2(self):
"""注冊(cè)失敗-兩次密碼不一致"""
data = ("miki123", "test123", "test321")
expected = {"code": 0, "msg": "兩次密碼輸入不一致!"}
result = register(*data)
self.assertEqual(expected, result)
# 如果直接運(yùn)行這個(gè)文件,需要使用unittest中的main函數(shù)來執(zhí)行測(cè)試用例
if __name__ == '__main__':
unittest.main()
? 上面?zhèn)鬟f測(cè)試數(shù)據(jù)處用到一個(gè)*解包,我在python基礎(chǔ)中有講過解包的原理和例子。
? 測(cè)試用例運(yùn)行結(jié)果如下,一共4條用例,其中通過3條,不通過1條,不通過的是本身注冊(cè)代碼的bug。
shell
Testing started at 21:58 ...
C:\software\python\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --target test_register.TestRegister
Launching unittests with arguments python -m unittest test_register.TestRegister in D:\learn\python_test
{'code': 1, 'msg': '注冊(cè)成功!'} != {'code': 0, 'msg': '用戶名和密碼必須在6-18位之間!'}
Expected :{'code': 0, 'msg': '用戶名和密碼必須在6-18位之間!'}
Actual :{'code': 1, 'msg': '注冊(cè)成功!'}
<Click to see difference>
Traceback (most recent call last):
File "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\teamcity\diff_tools.py", line 32, in _patched_equals
old(self, first, second, msg)
File "C:\software\python\lib\unittest\case.py", line 839, in assertEqual
assertion_func(first, second, msg=msg)
File "C:\software\python\lib\unittest\case.py", line 1138, in assertDictEqual
self.fail(self._formatMessage(msg, standardMsg))
File "C:\software\python\lib\unittest\case.py", line 680, in fail
raise self.failureException(msg)
AssertionError: {'code': 0, 'msg': '用戶名和密碼必須在6-18位之間!'} != {'code': 1, 'msg': '注冊(cè)成功!'}
- {'code': 0, 'msg': '用戶名和密碼必須在6-18位之間!'}
+ {'code': 1, 'msg': '注冊(cè)成功!'}
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\software\python\lib\unittest\case.py", line 59, in testPartExecutor
yield
File "C:\software\python\lib\unittest\case.py", line 615, in run
testMethod()
File "D:\learn\python24\python_base\day13_task\test_register.py", line 36, in test_username_lt6
self.assertEqual(expected, result)
Ran 4 tests in 0.007s
FAILED (failures=1)
Process finished with exit code 1
Assertion failed
TestFixure測(cè)試夾具
? unittest的測(cè)試夾具有兩種使用方式,一種是以測(cè)試方法為維度的setUp()
和tearDown()
,一種是以測(cè)試類為維度的setUpClass()
和tearDownClass()
。以注冊(cè)功能為例,但這個(gè)注冊(cè)代碼比較簡單,沒有真正需要用到測(cè)試夾具的地方,因此這只是個(gè)用法演示。
python
# test_register.py
import unittest
from register import register # 導(dǎo)入被測(cè)試的代碼
class TestRegister(unittest.TestCase):
"""注冊(cè)接口測(cè)試用例類"""
def setUp(self):
# 每條用例執(zhí)行之前都會(huì)執(zhí)行
print("用例{}開始執(zhí)行--".format(self))
def tearDown(self):
# 每條用例執(zhí)行之后都會(huì)執(zhí)行
print("用例{}執(zhí)行結(jié)束--".format(self))
@classmethod # 指明這是個(gè)類方法以類為維度去執(zhí)行的
def setUpClass(cls):
# 整個(gè)測(cè)試用例類中的用例執(zhí)行之前,會(huì)先執(zhí)行此方法
print("-----setup---class-----")
@classmethod
def tearDownClass(cls):
# 整個(gè)測(cè)試用例類中的用例執(zhí)行完之后,會(huì)執(zhí)行此方法
print("-----teardown---class-----")
def test_register_success(self):
"""注冊(cè)成功"""
data = ("mikitest", "miki123", "miki123") # 測(cè)試數(shù)據(jù)
expected = {"code": 1, "msg": "注冊(cè)成功!"} # 預(yù)期結(jié)果
result = register(*data) # 把測(cè)試數(shù)據(jù)傳到被測(cè)的代碼,接收實(shí)際結(jié)果
self.assertEqual(expected, result) # 斷言,預(yù)期和實(shí)際是否一致,一致即用例通過
def test_username_isnull(self):
"""注冊(cè)失敗-用戶名為空"""
data = ("", "miki123", "miki123")
expected = {"code": 0, "msg": "所有參數(shù)不能為空!"}
result = register(*data)
self.assertEqual(expected, result)
# 如果直接運(yùn)行這個(gè)文件,需要使用unittest中的main函數(shù)來執(zhí)行測(cè)試用例
if __name__ == '__main__':
unittest.main()
? 運(yùn)行結(jié)果:
shell
Testing started at 22:19 ...
C:\software\python\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --path D:/learn/python/test_register.py
Launching unittests with arguments python -m unittest D:/learn/python/test_register.py in D:\learn\python
-----setup---class-----用例test_pwd1_not_pwd2 (test_register.RegisterTestCase)開始執(zhí)行--
用例test_pwd1_not_pwd2 (test_register.RegisterTestCase)執(zhí)行結(jié)束--
用例test_register_success (test_register.RegisterTestCase)開始執(zhí)行--
用例test_register_success (test_register.RegisterTestCase)執(zhí)行結(jié)束--
用例test_username_isnull (test_register.RegisterTestCase)開始執(zhí)行--
用例test_username_isnull (test_register.RegisterTestCase)執(zhí)行結(jié)束--
用例test_username_lt6 (test_register.RegisterTestCase)開始執(zhí)行--
用例test_username_lt6 (test_register.RegisterTestCase)執(zhí)行結(jié)束--
-----teardown---class-----
Ran 4 tests in 0.003s
OK
Process finished with exit code 0
TestSuite測(cè)試套件
? unittest.TestSuite()類來表示一個(gè)測(cè)試用例集,把需要執(zhí)行的用例類或模塊存到一起,常用的方法如下:
- ?? unittest.TestSuite()
-
addTest()
:添加單個(gè)測(cè)試用例方法 -
addTest([..])
:添加多個(gè)測(cè)試用例方法,方法名存在一個(gè)列表
-
- ?? unittest.TestLoader()
-
loadTestsFromTestCase(測(cè)試類名)
:添加一個(gè)測(cè)試類 -
loadTestsFromModule(模塊名)
:添加一個(gè)模塊 -
discover(測(cè)試用例的所在目錄)
:指定目錄去加載,會(huì)自動(dòng)尋找這個(gè)目錄下所有符合命名規(guī)則的測(cè)試用例
-
python
# run_test.py,與test_register.py、register.py同一目錄下
import unittest
import test_register
# 第一步,創(chuàng)建一個(gè)測(cè)試套件
suite = unittest.TestSuite()
# 第二步:將測(cè)試用例,加載到測(cè)試套件中
# 方式1,添加單條測(cè)試用例
# case = test_register.TestRegister("test_register_success") # 創(chuàng)建一個(gè)用例對(duì)象,注意:通過用例類去創(chuàng)建測(cè)試用例對(duì)象的時(shí)候,需要傳入用例的方法名(字符串類型)
# suite.addTest(case) # 添加用例到測(cè)試套件中
# 方式2,添加多條測(cè)試用例
# case1 = test_register.TestRegister("test_register_success")
# case2 = test_register.TestRegister("test_username_isnull")
# suite.addTest([case1, case2]) # 添加用例到測(cè)試套件中
# 方式3,添加一個(gè)測(cè)試用例類
# loader = unittest.TestLoader() # 創(chuàng)建一個(gè)加載對(duì)象
# suite.addTest(loader.loadTestsFromTestCase(test_register.TestRegister))
# 方式4,添加一個(gè)模塊
loader = unittest.TestLoader() # 創(chuàng)建一個(gè)加載對(duì)象
suite.addTest(loader.loadTestsFromModule(test_register))
# 方式5,指定測(cè)試用例的所在的目錄路徑,進(jìn)行加載
# loader = unittest.TestLoader()
# suite.addTest(loader.discover(r"d:\learn\python"))
? 通常我們使用方式4、5比較多,你可以根據(jù)實(shí)際情況來運(yùn)用。其中方式5,還可以自定義匹配規(guī)則,默認(rèn)是會(huì)尋找目錄下test*.py
文件,即所有以test開頭命名的py文件,自定義如下:
python
loader = unittest.TestLoader()
suite.addTest(loader.discover(start_dir = r"d:\learn\python", pattern="test_case*.py")) # 匹配規(guī)則:所有以test_case開頭的
TestRunner執(zhí)行用例
? test runner顧名思義就是用來執(zhí)行測(cè)試用例的,并且可以生成相應(yīng)的測(cè)試報(bào)告。測(cè)試報(bào)告有兩種展示形式,一種是text文本,一種是html格式。
? html格式的就是HTMLTestRunner了,HTMLTestRunner
是 Python 標(biāo)準(zhǔn)庫的 unittest 框架的一個(gè)擴(kuò)展,它可以生成一個(gè)直觀清晰的 HTML 測(cè)試報(bào)告。使用的前提就是要下載?HTMLTestRunner.py,下載完后放在python的安裝目錄下的scripts目錄下即可。
? text文本相對(duì)于html來說過于簡陋,與控制臺(tái)輸出的沒有什么區(qū)別,也幾乎沒有人使用,這里不作演示,使用方法是一樣的。我們結(jié)合前面的測(cè)試套件來演示一下如何生成html格式的測(cè)試報(bào)告:
python
# run_test.py,與test_register.py、register.py同一目錄下
import unittest
import test_register
from HTMLTestRunner import HTMLTestRunner
# 創(chuàng)建測(cè)試套件
suite = unittest.TestSuite()
# 通過模塊加載測(cè)試用例
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromModule(test_register))
# 創(chuàng)建測(cè)試運(yùn)行程序啟動(dòng)器
runner = HTMLTestRunner(stream=open("report.html", "wb"), # 打開一個(gè)報(bào)告文件,將句柄傳給stream
tester="miki", # 報(bào)告中顯示的測(cè)試人員
description="注冊(cè)接口測(cè)試報(bào)告", # 報(bào)告中顯示的描述信息
title="自動(dòng)化測(cè)試報(bào)告") # 報(bào)告的標(biāo)題
# 使用啟動(dòng)器去執(zhí)行測(cè)試套件里的用例
runner.run(suite)
??相關(guān)參數(shù)說明:
-
stream
:指定輸出的方式 -
tester
:報(bào)告中要顯示的測(cè)試人員的名字 -
description
:報(bào)告中要顯示的面熟信息 -
title
:測(cè)試報(bào)告的標(biāo)題 -
verbosity
?:表示測(cè)試報(bào)告信息的詳細(xì)程度,一共三個(gè)值,默認(rèn)是2- 0 (靜默模式):你只能獲得總的測(cè)試用例數(shù)和總的結(jié)果,如:總共100個(gè) 失敗10 成功90
- 1 (默認(rèn)模式):類似靜默模式,只是在每個(gè)成功的用例前面有個(gè). 每個(gè)失敗的用例前面有個(gè)F
- 2 (詳細(xì)模式):測(cè)試結(jié)果會(huì)顯示每個(gè)測(cè)試用例的所有相關(guān)的信息
? 運(yùn)行完畢,你會(huì)發(fā)現(xiàn)你的項(xiàng)目目錄下已經(jīng)生成了一個(gè)report.html文件,在瀏覽器中打開,就可以查看測(cè)試報(bào)告了。
?
最后,綿薄之力
感謝每一個(gè)認(rèn)真閱讀我文章的人,雖然不是什么很值錢的東西,如果你用得到的話可以直接拿走:
?這些資料,對(duì)于【軟件測(cè)試】的朋友來說應(yīng)該是最全面最完整的備戰(zhàn)倉庫,這個(gè)倉庫也陪伴上萬個(gè)測(cè)試工程師們走過最艱難的路程,希望也能幫助到你!文章來源:http://www.zghlxwxcb.cn/news/detail-475222.html
資料獲取方式:
文章來源地址http://www.zghlxwxcb.cn/news/detail-475222.html
到了這里,關(guān)于unittest測(cè)試框架詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!