(2022.11.27 Sun)
unittest是Python自帶的單元測試框架。unittest+html和pytest+allure(測試報(bào)告)成為常用的自動(dòng)測試和報(bào)告的框架組合送浊。
概念
- test case測試用例:測試用例是測試的基本單元博秫,用于測試一組特定輸入的特定響應(yīng),unittest提供了基類
unittest.TestCase
用于創(chuàng)建測試案例异希。案例包括“輸入用戶名不輸入密碼健盒,則提示密碼為空”等。 - test fixture測試腳手架:為開展測試需要進(jìn)行的準(zhǔn)備工作称簿,以及所有相關(guān)的清理操作(cleanup actions)扣癣,比如創(chuàng)建臨時(shí)或代理數(shù)據(jù)庫、目錄憨降,啟動(dòng)一個(gè)服務(wù)器進(jìn)程等父虑。
- test suite測試套件:一系列的測試用例或測試套件,用于整合一些一起執(zhí)行的測試授药。
- test runner測試運(yùn)行器:用于執(zhí)行和輸出測試結(jié)果的組件士嚎,可使用圖形接口呜魄、文本接口,或返回運(yùn)行測試結(jié)果的特定值莱衩。
案例
# unittest_basic_example01.py
import logging
import unittest
class Login:
pass
class TestStringMethods(unittest.TestCase):
@classmethod
def setUpClass(cls):
# 必須使用@classmethod 裝飾器,所有test運(yùn)行前運(yùn)行一次
super().setUpClass()
logging.info("setUpClass")
@classmethod
def tearDownClass(cls):
# 必須使用@classmethod, 所有test運(yùn)行完后運(yùn)行一次
super().tearDownClass()
logging.info("tearDownClass")
def setUp(self):
# 每個(gè)測試用例執(zhí)行之后做操作
# do preparation
super().setUp()
logging.info("setUp")
def tearDown(self):
# 每個(gè)測試用例執(zhí)行之前做操作
super().tearDown()
logging.info("tearDown")
def test_upper(self):
logging.info('method test_upper is in progress')
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
logging.info('method test_isupper is in progress')
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
def test_split(self):
s = 'hello world'
logging.info('method test_split is in progress')
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)
if __name__ == '__main__':
format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO) #, datefmt="%H:%M:%S.%f"
unittest.main()
運(yùn)行結(jié)果
$ python unittest_basic_example01.py
2022-11-27 14:48:37,872: setUpClass
2022-11-27 14:48:37,873: setUp
2022-11-27 14:48:37,873: method test_isupper is in progress
2022-11-27 14:48:37,873: tearDown
.2022-11-27 14:48:37,873: setUp
2022-11-27 14:48:37,873: method test_split is in progress
2022-11-27 14:48:37,873: tearDown
.2022-11-27 14:48:37,873: setUp
2022-11-27 14:48:37,873: method test_upper is in progress
2022-11-27 14:48:37,873: tearDown
.2022-11-27 14:48:37,873: tearDownClass
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
該案例給出若干測試用例的結(jié)果和方法的執(zhí)行順序爵嗅。
案例中定義了單元測試類TestStringMethods
,在import unittest
之后笨蚁,定義該單元測試類之時(shí)睹晒,繼承unittest.TestCase
,使得該類成為一個(gè)unittest類括细。
unittest.TestCase
有四個(gè)基本方法
setUpClass
setUp
tearDownClass
tearDown
注意這四個(gè)方法針對不同的測試用例和測試用例類册招,兩兩成一對,即setUp
和tearDown
勒极,setUpClass
和tearDownClass
是掰。
setUpClass
和tearDownClass
都用@classmethod
裝飾器裝飾為類方法,這兩個(gè)方法分別在TestStringMethods
的所有test case之前和之后運(yùn)行一次辱匿。
setUp
和tearDown
則針對test case而言键痛,每個(gè)test case執(zhí)行前、后分別執(zhí)行這兩個(gè)方法匾七。用于做測試的準(zhǔn)備和收尾工作絮短。
(2022.12.17 Sat)這四個(gè)方法都用于對測試的準(zhǔn)備工作,如setUpClass
和tearDownClass
用于在測試類對象開始執(zhí)行之前對類做初始化準(zhǔn)備工作和收尾工作(如關(guān)閉什么)昨忆。setUp
和tearDown
針對test case做初始化和收尾工作丁频。
test case都以test
作為方法的開始,這個(gè)命名傳統(tǒng)用于向test runner通知哪些方法代表著test case邑贴。
觀察測試結(jié)果席里,兩個(gè)test case中, test_isupper
先于test_split
執(zhí)行拢驾。默認(rèn)情況下奖磁,test case的執(zhí)行順序?yàn)榉椒淖值湫?alphabetical)。同時(shí)還有其他若干方法可以調(diào)整test case的執(zhí)行順序繁疤。
斷言assert
test case中最常用的斷言方法
method | checks that | new in |
---|---|---|
assertEqual(a, b) |
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 |
3.1 |
assertIsNot(a, b) |
a is not b |
3.1 |
assertIsNone(x) |
x is None |
|
assertIsNotNone(x) |
x is not None |
|
assertIn(a, b) |
a in b |
3.1 |
assertNotIn(a, b) |
a not in b |
3.1 |
assertIsInstance(a, b) |
isinstance(a, b) |
3.2 |
assertNotIsInstance(a, b) |
not isinstance(a, b) |
3.2 |
assertRaises(xxxError) |
如何測試拋出異常 How to Raise an exception in unit test
(2023.02.11 Sat)
使用unittest
中的assertRaises
方法咖为。考慮下面案例test_add_fish_to_aquarium.py
import unittest
def add_fish_to_aquarium(fish_list):
if len(fish_list) > 10:
raise ValueError("A maximum of 10 fish can be added to the aquarium")
return {"tank_a": fish_list}
class TestAddFishToAquarium(unittest.TestCase):
def test_add_fish_to_aquarium_success(self):
actual = add_fish_to_aquarium(fish_list=["shark", "tuna"])
expected = {"tank_a": ["shark", "tuna"]}
self.assertEqual(actual, expected)
def test_add_fish_to_aquarium_exception(self):
too_many_fish = ["shark"] * 25
with self.assertRaises(ValueError) as exception_context:
add_fish_to_aquarium(fish_list=too_many_fish)
self.assertEqual(
str(exception_context.exception),
"A maximum of 10 fish can be added to the aquarium"
)
在該案例中被測試函數(shù)add_fish_to_aquarium
檢測輸入變量長度稠腊,如果超過10則返回ValueError
躁染,和提示信息。在測試部分架忌,使用context manager吞彤,執(zhí)行被測試函數(shù),即
with self.assertRaises(ValueError) as exception_context:
add_fish_to_aquarium(fish_list=too_many_fish)
此時(shí)會(huì)拋出異常并保存在異常對象exception_context
中鳖昌。接下來判斷異常對象中內(nèi)容和函數(shù)內(nèi)的異常信息是否一致
self.assertEqual(str(exception_context.exception), 'A maximum of 10 fish can be added to the aquarium')
至此可以實(shí)現(xiàn)測試代碼中對異常的測試备畦。運(yùn)行在終端該文本
>> python -m unittest test_add_fish_to_aquarium.py
Output
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
TestCase的執(zhí)行順序
(2022.12.17 Sat)
unittest中各個(gè)test case的執(zhí)行順序如前面所述,按照以test_
開始的test case名字的字典順序執(zhí)行许昨。比如上一部分的案例中懂盐,test case共三個(gè)test_upper
、test_isupper
糕档、test_split
莉恼。按test_
之后名稱的字典序,則其排序?yàn)?code>test_isupper速那、test_split
和test_upper
俐银,而這也是運(yùn)行結(jié)果中test case的排序。
除此之外端仰,還有其他方法可以設(shè)定test case的排序
加序號
在test case的名字中加入預(yù)先設(shè)定的序號捶惜,執(zhí)行時(shí)按照序號的順序執(zhí)行。
import logging
import unittest
class SeqOrder(unittest.TestCase):
def test_3(self):
logging.info('method step3')
def test_1(self):
logging.info('method step1')
def test_2(self):
logging.info('method step2')
if __name__ == '__main__':
format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO) #, datefmt="%H:%M:%S.%f"
unittest.main()
運(yùn)行結(jié)果為
% python unittest_basic_example03.py
2022-12-17 12:07:58,319: method step1
.2022-12-17 12:07:58,319: method step2
.2022-12-17 12:07:58,319: method step3
.
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
Monolithic test
將test case結(jié)合為一個(gè)整體荔烧,運(yùn)行時(shí)按整體內(nèi)部的test case排序運(yùn)行吱七。在下面案例中,test case名字不以test_
作為開頭鹤竭,但經(jīng)過self._steps
方法排序(dir(self)
)踊餐,在執(zhí)行時(shí)(test_steps
),調(diào)用了self._steps
生成器臀稚,依次執(zhí)行test case吝岭。這種方法類似于在test case的名字中按開發(fā)者意圖加入序號并按序號執(zhí)行。
import logging
import unittest
class Monolithic(unittest.TestCase):
def step3(self):
logging.info('method step3')
def step1(self):
logging.info('method step1')
def step2(self):
logging.info('method step2')
def _steps(self):
for name in dir(self): # dir() result is implicitly sorted
if name.startswith("step"):
yield name, getattr(self, name)
def test_steps(self):
for name, step in self._steps():
try:
step()
except Exception as e:
self.fail("{} failed ({}: {})".format(step, type(e), e))
if __name__ == '__main__':
format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO) #, datefmt="%H:%M:%S.%f"
unittest.main()
運(yùn)行結(jié)果
% python unittest_basic_example02.py
2022-12-17 11:34:38,555: method step1
2022-12-17 11:34:38,556: method step2
2022-12-17 11:34:38,556: method step3
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
上面代碼中dir(self)
返回該類的內(nèi)部對象吧寺,并按序輸出窜管。
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', '_steps', 'step1', 'step2', 'step3', 'test_steps']
TestSuite
在test suite中加入test case,加入順序即執(zhí)行順序稚机。
import logging
import unittest
class TestOrder(unittest.TestCase):
def test_1(self):
logging.info('method step1')
def test_2(self):
logging.info('method step2')
class OtherOrder(unittest.TestCase):
def test_4(self):
logging.info('method step4')
def test_3(self):
logging.info('method step3')
def suite():
suite = unittest.TestSuite()
suite.addTest(OtherOrder('test_4'))
suite.addTest(TestOrder('test_2'))
suite.addTest(OtherOrder('test_3'))
suite.addTest(TestOrder('test_1'))
return suite
if __name__ == '__main__':
format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO) #, datefmt="%H:%M:%S.%f"
runner = unittest.TextTestRunner(failfast=True)
runner.run(suite())
運(yùn)行結(jié)果如下
% python unittest_basic_example04.py
2022-12-17 12:24:12,820: method step4
.2022-12-17 12:24:12,820: method step2
.2022-12-17 12:24:12,820: method step3
.2022-12-17 12:24:12,820: method step1
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
Reference
1 經(jīng)驗(yàn)分享:自動(dòng)化測試框架之unittest微峰,測試小濤
2 unittest教程(2w字實(shí)例合集)——Python自動(dòng)化測試一文入門,是羽十八ya
3 python unittest official doc
4 Python unittest.TestCase execution order, stackoverflow