unittest框架
- unittest是python标准库中的模块
- unittest中的几个重要概念
testcase
:测试用例,所有测试用例都要继承unittest.TestCase类testfixture
:测试开始的准备工作和测试结束后的清理工作testsuite
:测试套件,一系列的测试用例或者测试套件testruuner
:用于执行和输出测试结果的组件
textfixture
setUp
:测试用例的前置操作,运行时每个测试用例均会执行,如果setUp运行发生异常,测试方法不会运行
tearDown
: 只要setUp成功运行无论测试方法是否成功,都会运行tearDown
setUpClass
:测试类的前置操作,@classmethod修饰,和setUp类似
tearDownClass
:和tearDown类似
setUpModule
: 测试模块的前置操作
tearDownModule
:和tearDown类似
例子1
- textfixture的执行顺序
测试代码
import unittest
class testCase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
print("setupClass")
def setUp(self) -> None:
print("setUp")
@classmethod
def tearDownClass(cls) -> None:
print("teardownClass")
def tearDown(self) -> None:
print("teardown")
def testCaseTwo(self):
print("testCaseTwo")
self.assertTrue(True)
def testCaseOne(self):
print("testCaseOne")
self.assertTrue(True)
class testTwo(unittest.TestCase):
def testcase2(self):
print("testcase2")
self.assertTrue(True)
def setUpModule():
print("setUpmodule")
def tearDownModule():
print("tearDownmodule")
if __name__=="__main__":
unittest.main()
运行结果
D:\Python\program\practic_unittest>python -m unittest test_case.py -v
setUpmodule
setupClass
testCaseOne (mylib.testCase) ... setUp
testCaseOne
teardown
ok
testCaseTwo (mylib.testCase) ... setUp
testCaseTwo
teardown
ok
teardownClass
testcase2 (mylib.testTwo) ... testcase2
ok
tearDownmodule
----------------------------------------------------------------------
Ran 3 tests in 0.012s
OK
例子2
- 修改setUp,让setUp运行失败,可看到测试用例抛出AssertionError:setUp fail的异常
测试代码
def setUp(self) -> None:
self.fail("setUp fail")
print("setUp")
执行结果
- 测试用例未执行:
D:\Python\program\practic_unittest>python -m unittest test_case.py -v
setupClass
testCaseOne (test_case.testCase) ... FAIL
testCaseTwo (test_case.testCase) ... FAIL
teardownClass
======================================================================
FAIL: testCaseOne (test_case.testCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Python\program\practic_unittest\test_case.py", line 8, in setUp
self.fail("setUp fail")
AssertionError: setUp fail
======================================================================
FAIL: testCaseTwo (test_case.testCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Python\program\practic_unittest\test_case.py", line 8, in setUp
self.fail("setUp fail")
AssertionError: setUp fail
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=2)
testcase
- 测试方法的命名都以 test 开头,并且testcase必需在测试类中,并且测试类要继承unittest.TestCase
- 默认匹配test*.py的文件中的测试用例.可以修改,详见unittest.TestLoader类中的discover方法
断言
常见的有以下断言,更多的断言详见https://docs.python.org/3.8/library/unittest.html#assert-methods
assertEqual(a, b, msg=None)
检查a的值是否等于b的值
assertNotEqual(a, b, msg=None)
assertTrue(x, msg=None)
检查bool(x)是否为True
assertFalse(x, msg=None)
检查bool(x)是否为False
assertIs(a, b, msg=None)
检查a和b是否是同一个对象(存储地址是否一样),是则断言通过
assertIsNot(a, b, msg=None)
检查a和b是否是同一个对象,不是则断言通过
assertIsNone(x, msg=None)
检查x是否是None
assertIsNotNone(x, msg=None)
检查x是否不是None
assertIn(a, b, msg=None)
检查b是否包含a,是则断言通过
assertNotIn(a, b, msg=None)
检查b是否包含a,否则断言通过
assertIsInstance(a, b, msg=None)
检查a是否是b的实例,是则通过
assertNotIsInstance(a, b, msg=None)
检查a是否是b的实例,不是则通过
跳过测试
- 可对测试方法或者测试类添加装饰器,以实现跳过测试
- 亦可对setup添加跳过测试
- 包含以下装饰器:
@unittest.skip(reason)
被装饰的用例跳过执行
@unittest.skipIf(condition, reason)
只有满足条件了才会跳过,不满足条件则执行用例
@unittest.skipUnless(condition, reason)
满足条件不跳过,执行用例,不满足条件则跳过
例子
- test_unittest.py文件
import unittest
class testStringMethodone(unittest.TestCase):
@unittest.skipIf(mylib.__version__ < (1, 3),
"not supported in this library version")
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
@unittest.skipUnless(sys.platform.startswith("win"), "requires windows")
def test_isupper(self):
self.assertTrue('FOO'.isupper())
@unittest.skip("decorate skip")
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
if __name__ == "__main__":
unittest.main()
- mylib.py文件
__version__=(1,2)
- 执行结果
PS D:\Python\program\practic_unittest> python -m unittest test_unittest.py -v
True
test_isupper (test_unittest.testStringMethodone) ... ok
test_split (test_unittest.testStringMethodone) ... skipped 'decorate skip'
test_upper (test_unittest.testStringMethodone) ... skipped 'not supported this library version'
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK (skipped=2)
预计的失败
@unittest.expectedFailure
标记测试用例失败.如果运行有异常,则运行成功,如果运行通过,则运行失败
例子
- test_unittest.py文件
import unittest
class testStringMethodone(unittest.TestCase):
......
@unittest.expectedFailure
def test_capitalize(self):
self.assertEqual("test".capitalize(), "Test1")
- 运行结果
PS D:\Python\program\practic_unittest> python -m unittest test_unittest.py -v
True
test_capitalize (test_unittest.testStringMethodone) ... expected failure
.....
subtest
subTest( msg=None , **params )
,msg和params是可选值,当运行失败时会回显这些值- subtest返回一个上下文管理器,其下的代码作为子测试,运行失败时,后续测试还会继续,如果不使用subtest,第一次遇到执行失败时就停止了
- 有运行失败的测试用例才会有提示,如果没有直接显示通过
class testStringMethodone(unittest.TestCase):
def test_notuse_subtest(self):
for i in ['str1', "test",'str3']:
self.assertIn("test",i)
def test_use_subtest(self):
for i in ['str1', "test",'str3']:
with self.subTest(i):
self.assertIn("test",i)
运行结果:
testsuite& 测试执行
- 方法一:使用unittest.main(),收集模块中的所有测试用例并执行
if __name__=="__main__":
unittest.main()
- 方法二:通过testsuite收集测试用例
- 添加test_case.py:测试用例文件
import unittest
class testclassOne(unittest.TestCase):
def testCaseTwo(self):
print("testCaseTwo")
self.assertTrue(True)
def testCaseOne(self):
print("testCaseOne")
assert False
class testclassTwo(unittest.TestCase):
def test1(self):
self.assertTrue(False)
- run_testcase.py:执行文件
# coding:utf-8
import unittest
import test_case
import os
def load_by_testcase(): #加载指定的testcase
suite = unittest.TestSuite()
suite.addTest(test_case.testclassOne('testCaseTwo'))
suite.addTest(test_case.testclassTwo('test1'))
return suite
def load_by_calss(): #通过测试类来添加
suite = unittest.TestSuite()
loader=unittest.TestLoader()
load_suite=loader.loadTestsFromTestCase(test_case.testclassOne)
suite.addTests(load_suite)
return suite
def load_by_pattern(pattern): #通过匹配的文件执行用例
this_dir = os.path.dirname(__file__)
suite = unittest.TestSuite()
loader=unittest.TestLoader()
package_tests = loader.discover(this_dir,pattern=pattern)
suite.addTests(package_tests)
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
#test_suite = load_by_testcase()
#test_suite=load_by_calss()
test_suite=load_by_pattern("*case.py")
runner.run(test_suite)
- 执行结果
PS D:\Python\program\practic_unittest> python run_testcase.py
testCaseOne
FtestCaseTwo
.F
======================================================================
FAIL: testCaseOne (test_case.testclassOne)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Python\program\practic_unittest\test_case.py", line 9, in testCaseOne
assert False
AssertionError
======================================================================
FAIL: test1 (test_case.testclassTwo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Python\program\practic_unittest\test_case.py", line 12, in test1
self.assertTrue(False)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=2)
输出报告
HTMLTestRunner
- 安装HTMLTestRunner:
pip install HTMLTestRunner-Python3
- 添加和修改如下代码
from HTMLTestRunner import HTMLTestRunner
if __name__ == '__main__':
testsuite = load_by_pattern("*case.py")
report_name = 'result_{}.html'.format(time.strftime("%Y.%m.%d_%H_%M_"))
f=open(report_name,'w',encoding='utf-8') #最好添加encoding='utf-8'这个参数,避免输出的报告出现中文乱码的情况
runner = HTMLTestRunner.HTMLTestRunner(stream=f,title='测试报告',description='详情')
runner.run(testsuite)
- 输出结果
BeautifulReport
- 安装BeautifulReport:
pip install BeautifulReport
- 添加和修改如下代码
from BeautifulReport import BeautifulReport
if __name__ == '__main__':
testsuite = load_by_pattern("*case.py")
run = BeautifulReport(testsuite)
report_name ='result_{}.html'.format( time.strftime("%Y.%m.%d_%H_%M_"))
run.report(description='beautifulreport', filename=report_name)
- 输出报告
命令行执行
python -m unittest
执行可找到的所有testcase,等价python -m unittest discover
python -m unittest test_module/test_module.TestClass/test_module.TestCalss.test_method
指定运行的模块/类/测试方法
python -m unittest tests/test_something.py
测试模块亦可通过测试文件路径指定
-v
:输出详细结果
-f
:第一个错误或者失败时就停止测试
-k
:只运行匹配的testcase,eg:-k name,只匹配模式或测试方法和类,并且大小写敏感
探索性测试
python -m unittest discover
:只会匹配test*.py文件
参数:
-v, --verbose
:更详细地输出结果。
-s, --start-directory directory
:开始进行搜索的目录(默认值为当前目录 . )。
-p, --pattern pattern
:用于匹配测试文件的模式(默认为 test*.py )。
-t, --top-level-directory directory
:指定项目的最上层目录(通常为开始时所在目录)