pytest 学习使用
pytest测试发现规则
pytest
将在当前目录及其子目录中运行所有格式为test _*.py
或* _test.py
的文件,收集测试项目:
- test 类之外的带前缀测试功能或方法
- test前缀测试Test类中的前缀测试函数或方法(无
__init__
方法)
常用命令行参数
-
-v
:pytest -v
,输出用例更加详细的执行信息,比如用例所在的文件及用例名称等 -
-r
:pytest -r
在测试会话结束时显示“简短的测试摘要信息”,选项后接有多个字符,默认情况下列出fE
:失败和错误选项后参数 例子 作用 f
pytest -rf
失败 E
pytest -rE
错误 s
pytest -rs
跳过 x
pytest -rx
xfailed
X
pytest -rX
xpass
p
pytest -rp
通过 P
pytest -rP
通过输出 a
pytest -ra
除 pP
的所有字符A
pytest -rA
全部 N
pytest -rN
无,可用于不显示任何内容( fE
默认设置) -
-q
:pytest -q
,输出用例简略的执行信息。 -
-m
:pytest -m name_of_mark
,打印自定义标记测试。$ pytest -m slow # 运行标记为slow的测试
-
-k
:pytest -k 'test_method or test_other'
,仅运行与给定字符串表达式匹配的测试,匹配字符串不区分大小写。$ pytest -k 'test_method or test_other' # 匹配包含test_method或test_other的测试方法和测试类 $ pytest -k 'not test_method' # 匹配不包含test_method的测试方法和测试类 $ pytest -k 'not test_method and not test_other' # 匹配不包含test_method和test_other的测试方法和测试类
-
-x
:pytest -x
,遇到错误(第一个错误)立即停止测试。 -
--maxfail
:`pytest --maxfail=N,遇到第N个错误立即停止测试。$ pytest -x #在第一个失败后停止测试 $ pytest --maxfail=2 #在第2个失败后停止测试
-
--durations=num -vv
:pytest --durations=num -vv
,num为0时则倒序显示所有的用例,为具体值则显示耗时最长的对应该数量的用例,-vv 显示持续时间为0秒的用例,会按用例执行耗时时长:从长到短 显示结果,用于调优测试代码。$ pytest --durations=3 -vv # 要获取最慢的3个测试持续时间为0秒的列表 ===================================== slowest 3 test durations ===================================== 0.00s call test_example.py::test_fail 0.00s setup test_example.py::test_error 0.00s setup test_example.py::test_fail
-
--resultlog
:pytest --resultlog=path
,生成普通的结果文件。$ pytest test_example.py --resultlog=./test.log # 执行测试并在当前目录生成test.log结果文件(普通的结果文件)
-
--junitxml
:pytest --junitxml=path
,生成Junit
结果文件,可被Jenkins
其他持续集成服务器读取的结果文件。$ pytest test_example.py --junitxml=./report.xml # 执行测试并在当前目录生成report.xml结果文件(Junit结果文件)
--html
:pytest --html=path
,生成html
结果文件,可使用Jenkins
的HTML插件读取展示。$ pytest test_example.py --html=./report.html # 执行测试并在当前目录生成report.html结果文件(html结果文件)
断言
断言assert
语句
pytest
允许您使用标准python assert
来验证Python测试中的期望和值。
Python assert(断言)用于判断一个表达式,**在表达式条件为 false 的时候触发断言异常。**断言可以在条件不满足程序运行的情况下直接返回错误,而不必等待程序运行后出现崩溃的情况
语法格式:assert expression
,等价于:
if not expression:
raise AssertionError
assert
后面也可以紧跟参数:assert expression [, arguments]
,相当于:
if not expression:
raise AssertionError(arguments)
预期异常的断言
- 使用
pytest.raises()
触发期望断言
# 测试没有返回ValueError或者没有异常返回时,断言判断失败,否则断言成功
import pytest
def myfunc():
raise ValueError("Exception 123 raised")
def test_match():
with pytest.raises(ValueError):
myfunc() # 断言成功,pass
还可访问异常的属性,常用的属性包括:.type
、.value
和.traceback
import pytest
def myfunc():
raise ValueError("Exception 123 raised")
def test_match():
with pytest.raises(ValueError) as excinfo:
myfunc()
assert '123' in str(excinfo.value)
- 使用
pytest.mark.xfail()
接收raises
参数,来判断用例是否因为一个具体的异常而导致失败
如果
f()
触发一个IndexError
异常,则用例标记为xfailed
;如果没有,则正常执行f()
;
import pytest
def f():
raise IndexError
@pytest.mark.xfail(raises=IndexError)
def test_f():
f()
注意:
- 如果
f()
测试成功,用例的结果是xpassed
,而不是passed
;pytest.raises
适用于检查由代码故意引发的异常;而@pytest.mark.xfail()
更适合用于记录一些未修复的Bug
;
函数标记功能pytest.mark
通过文件注册自定义标记
-
在
pytest.ini
文件中注册自定义标记# content of pytest.ini [pytest] markers = 标签1: 标签说明 标签2: 标签说明 ...
-
在
conftest.py
注册自定义标记# content of conftest.py def pytest_configure(config): config.addinivalue_line( "markers", "env(name): mark test to run only on named environment" )
使用@pytest.mark.name_of_the_mark
装饰器注册自定义标记
使用
@pytest.mark.name_of_the_mark
装饰器上应用的未注册标记将始终发出警告,建议使用前两种方式
@pytest.mark.slow # 注册slow的标记
def test_func():
...
跳过用例 @pytest.mark.skip
/@pytest.mark.skipif
-
@pytest.mark.skip
-
@pytest.mark.skipif
skip
和skipif
可标记无法在某些平台上运行的测试功能,或希望失败的测试功能。要给跳过的测试添加理由和条件,应当使用skipif
。
标记函数参数化@pytest.mark.parametrize
可用于在测试用例方法前添加测试数据。
-
传入单个参数,
@pytest.mark.parametrize('参数名',lists)
-
传入多个参数,@pytest.mark.parametrize(‘参数1’,‘参数2’,[(参数1_data[0],参数2_data[0]),参数1_data[1],参数2_data[1])]
list的每个元素都是一个元祖,元祖里的每个元素和按参数顺序一一对应。
- 例:传入两个参数
import pytest
@pytest.mark.parametrize("test_input,expected",
[ ("3+5", 8),
("2+4", 6),
("6 * 9", 42),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
- 例:传入参数组合
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
print("测试数据组合:x->%s, y->%s" % (x, y))
输出:
test_example.py::test_foo[2-0] PASSED [ 25%]测试数据组合:x->0, y->2
test_example.py::test_foo[2-1] PASSED [ 50%]测试数据组合:x->1, y->2
test_example.py::test_foo[3-0] PASSED [ 75%]测试数据组合:x->0, y->3
test_example.py::test_foo[3-1] PASSED [100%]测试数据组合:x->1, y->3
测试准备与结束
setup
-可用于测试准备
teardown
-可用于测试结束的数据清理/环境重置等
用例运行级别
- **模块级(
setup_module
/teardown_module
)**开始于模块始末,全局的 - **函数级(
setup_function
/teardown_function
)**只对函数用例生效(不在类中) - **类级(
setup_class
/teardown_class
)**只在类中前后运行一次(在类中) - **方法级(
setup_method
/teardown_method
)**开始于方法始末(在类中) - **类里面的(
setup
/teardown
)**运行在调用方法的前后
setup_method
和teardown_method
的功能和setup
/teardown
功能是一样的,一般二者用其中一个即可。
模块级
# 模块级准备与结束,setup_module()在测试模块前执行测试用例一次,teardown_module()在测试模块测试用例结束执行后一次
# content of test_one.py
import pytest
import sys
def setup_module():
print("setup_module:整个.py模块只执行一次")
def teardown_module():
print("teardown_module:整个.py模块只执行一次")
def setup_function():
print("setup_function:每个用例开始前都会执行")
def teardown_function():
print("teardown_function:每个用例结束后都会执行")
def test_one():
print(f"正在执行----{sys._getframe(0).f_code.co_name}")
x = "this"
assert 'h' in x
def test_three():
print(f"正在执行----{sys._getframe(0).f_code.co_name}")
a = "hello"
b = "hello world"
assert a in b
if __name__ == "__main__":
pytest.main(["-v"])
输出:
============================= test session starts =============================
platform win32 -- Python 3.6.7rc1, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: ..., configfile: pytest.initest_first.py::test_one
test_first.py::test_three
collected 2 items
test_first.py setup_module:整个.py模块只执行一次
setup_function:每个用例开始前都会执行
.正在执行----test_one
teardown_function:每个用例结束后都会执行
setup_function:每个用例开始前都会执行
.正在执行----test_three
teardown_function:每个用例结束后都会执行
teardown_module:整个.py模块只执行一次
[100%]
==========