单元测试框架
- 测试发现:从多个
py
文件中收集并加载测试用例
- 测试执行:将测试用例按照一定的顺序和条件取执行并生成结果
- 测试判断:通过断言取判断结果是否正确
- 测试报告:统计测试进度,通过率,生成报告
一 unittest
1. 重要组件
TestCase
TestSuite
TestFixTure
TestLoader
TestRunner
2. TestCase
| import unittest
class EcshopLogin(unittest.TestCase):
# 测试用例
def test01_register(self):
print("注册测试")
def test02_login(self):
print("登录测试")
|
1. 使用命令行方式运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | # python -m unittest 文件名
python -m unittest ecshop_login.py
注册测试
.登录测试
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
# python -m unittest 模块名.类名
python -m unittest ecshop_login.EcshopLogin
注册测试
.登录测试
.
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
# python -m unittest 模块名.类名.方法名
python -m unittest ecshop_login.EcshopLogin.test02_login
登录测试
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
# python -m unittest -v 模块名.类名.方法名
# -v 显示详细
python -m unittest -v ecshop_login.EcshopLogin.test02_login
test02_login (ecshop_login.EcshopLogin) ... 登录测试
ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
|
2. 执行结果分析
3. 用例执行顺序
按照ASCII码的规则:0-9 A-Z a-z
4. 使用TestSuite
只运行部分用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | import unittest
class EcshopLogin(unittest.TestCase):
# 测试用例
def test01_register(self):
print("注册测试")
def test02_login(self):
print("登录测试")
if __name__ == '__main__':
suite = unittest.TestSuite()
# 添加测试单元方式一
suite.addTest(EcshopLogin("test02_login"))
# 添加测试单元方式二
#testcases = [EcshopLogin("test02_login")]
#suite.addTests(testcases)
# 添加测试单元方式三
#testcases = unittest.defaultTestLoader.discover(start_dir=os.getcwd(), pattern="test*.py")
#suite.addTests(testcases)
# 运行方式一
# unittest.main(defaultTest='suite')
# 运行方式二
unittest.TextTestRunner().run(suite)
|
| python ecshop_login.py
登录测试
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
|
5. Testfixture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | import unittest
def setUpModule():
print("start Module TestFixture")
def tearDownModule():
print("end Module TestFixture")
class EcshopLogin(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
print("setUpClass: 在每个类之前执行一次")
def setUp(self) -> None:
print("setUp: 在每个测试用例之前的准备工作")
def test01_register(self):
print("注册测试")
def test02_login(self):
print("登录测试")
def tearDown(self) -> None:
print("tearDown: 在每个测试用例之后的清理工作")
@classmethod
def tearDownClass(cls) -> None:
print("tearDownClass: 在每个类之后的执行一次")
if __name__ == '__main__':
unittest.main()
|
执行结果
| $ python ecshop_login.py
start Module TestFixture
setUpClass: 在每个类之前执行一次
setUp: 在每个测试用例之前的准备工作
注册测试
tearDown: 在每个测试用例之后的清理工作
.setUp: 在每个测试用例之前的准备工作
登录测试
tearDown: 在每个测试用例之后的清理工作
.tearDownClass: 在每个类之后的执行一次
end Module TestFixture
|
6. 忽略测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | import unittest
@unittest.skip("此模块忽略测试")
class EcshopLogin(unittest.TestCase):
age = 20
@unittest.skip("无条件忽略")
def test01_register(self):
print("注册测试")
@unittest.skipIf(age > 18, "条件为真忽略")
def test02_login(self):
print("登录测试")
@unittest.skipUnless(age > 18, "条件为假忽略")
def test03_logout(self):
print("登出测试")
if __name__ == '__main__':
unittest.main()
|
7. 断言
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | import unittest
class EcshopLogin(unittest.TestCase):
def test01_register(self):
self.assertTrue([])
def test02_login(self):
self.assertEqual(1, 2)
if __name__ == '__main__':
unittest.main()
|
8. DDT装饰器
| @ddt # 类装饰器,声明当前类使用DDT框架
@data # 函数装饰器,用于给测试用例传递参数
@unpack # 函数装饰器,将传输的数据包解包,一般作用于元组和列表
@file_data # 函数装饰器,可直接读取yaml/json文件
|
@data
的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | import unittest
import ddt as ddt
@ddt.ddt()
class LoginTest(unittest.TestCase):
@ddt.data('tom') # 传几个值,用例执行几次
def test01_login(self, name):
print(name)
if __name__ == '__main__':
unittest.main()
|
@unpack
的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | import unittest
import ddt as ddt
@ddt.ddt()
class LoginTest(unittest.TestCase):
@ddt.data(("tom", 18), ("jerry", 16))
@ddt.unpack
def test01_login(self, name, age):
print(name, age)
if __name__ == '__main__':
unittest.main()
|
9. 源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | class TestProgram(object):
def __init__(self,
module='__main__', # 测试用例所在的路径
defaultTest=None, # 待测试用例的名称,默认所有
argv=None, # 接收外部参数
testRunner=None, # 测试运行器,默认为TextTestRunner
testLoader=loader.defaultTestLoader, # 指定默认测试加载器
exit=True, # 是否在测试完成后结束python程序
verbosity=1, # 类似于命令行-v [0|1|2]
failfast=None,
catchbreak=None,
buffer=None,
warnings=None,
*,
tb_locals=False):
|
二、pytest
1. 编写规则
| import pytest
class TestLogin:
def test_01_login(self):
print("登录测试")
if __name__ == '__main__':
pytest.main(['-sv'])
|
2 执行
1. 执行方式
1. 主函数模式
- 执行所有:
pytest.main()
- 指定模块:
pytest.main(['-sv','test_login.py']
- 指定目录:
pytest.main(['-sv', './testcases_dir'])
- 通过nodeid指定:nodeid由模块名,分隔符,类名,方法名,函数名组成
pytest.main(['-sv', 'test_login.py::TestLogin::test_01_login'])
2.命令行模式
- 执行所有:
pytest
- 指定模块:
pytest -sv test_login.py
- 指定目录:
pytest -sv ./testcases_dir
- 通过nodeid指定:nodeid由模块名,分隔符,类名,方法名,函数名组成
pytest -sv test_login.py::TestLogin::test_01
_login
2. 常用参数
| -s: 表示输出调试信息,包括print打印的信息
-v: 显示更详细的信息
-n 分布式运行
|
单元测试框架对比
1. 用例编写规则
unittest
: 提供了testcase
(测试用例)、testsuites
(测试套件)、testfixtures
(测试固件/夹具)、testloader
(测试加载器)、testrunner
(测试运行器)。必须遵守以下规则:
- 测试文件必须导入:
import unittest
- 测试类必须继承
unittest.TestCase
- 测试方法必须以
test
开头
pytest
:第三方测试框架,基于unittest
的扩展框架,必须遵守以下规则:
- 测试文件名必须以
test_
开头或者_test
结尾
- 测试类必须以
Test
开头
- 测试方法必须以
test
开头
2. 用例的前置和后置
unittest
:
setUp
/tearDown
在每个用例之前/之后运行一次。如打开浏览器,加载/关闭网页
setUpClass
/teatDownClass
在每个类运行之前/之后运行一次。如创建/关闭数据库连接,创建/销毁日志对象
setUpModule
/tearDownModule
在每个模块之前/之后执行一次
pytest
:
- 方法级
setup_method
/teardown_method
在方法之前/之后。优先级高
setup
/teardown
在方法之前/之后。优先级低
- 函数级
setup_function
/teardown_function
在函数之前/之后
- 类级
setup_class/teardown_class
在类之前/之后
- 模块级
setup_module/teardown_module
在模块之前/之后
- 装饰器:可以在函数之前加
pytest_fixture()
实现上述的功能
3. 断言
unittest
: assertTrue
、assertEqual
、assertin
pytest
: assert
4. 报告
unittest
: htmltestrunner
pytest
:插件(pytest-HTML
、allure
)
5. 失败重跑
unittest
:无
pytest
: pytest-rerunfailures
插件
6. 数据驱动
unittest
: DDT
pytest
: @pytest.mark.parametrize
装饰器
7. 用例分类执行
unittest
:默认执行所有,也可以用过testsuite
来执行部分用力,或者-k参数
pytest
:@pytest.mark