pytest 版本遵循 () 语义控制。
昨天发布的 pytest 8.0 是全新的major版本,
意味本次更新有重大变更,以及向后不兼容的修改!
1. 重大变更
01
移除历史包袱
一大批弃用警告升级为错误,从而无法继续使用,并将在8.1正式从代码中移除。
如果你还没有了解新版变化,手滑升级到了8.0后无法正常运行,可以通过屏蔽警告的方式,暂时过渡
[pytest]filterwarnings = ignore::pytest.PytestRemovedIn8Warning
注意,这是一个临时解决方案,8.1 之后这个方法也会失效
02
Python >=3.8
Python 3.7 生命周期在 2023 年 6 月 27 日已终止。
pytest 自本次更新之后,放弃了对 python 3.7 的兼容。
03
pluggy>=1.3.0
pluggy 1.3 引入了新的基于生成器的新式钩子包装器(hookwrapper)
。
在旧式的钩子包装器中,获取和修改钩子结果,使用了面向对象式的方式:
@pytest.hookimpl(hookwrapper=True)def pytest_pyfunc_call(pyfuncitem):
outcome = yield # <--------- 生成器方式接收对象
res = outcome.get_result() # <--- 【面向对象】方式获取旧结果
new_res = post_process_result(res)
outcome.force_result(new_res) # <--- 【面向对象】方式设置新结果
(左右可以滑动)
而在新式的钩子包装其中,获取和修改钩子结果,直接使用生成器自身的语法
@pytest.hookimpl(wrapper=True) # 使用新参数申明 新式包装器def pytest_pyfunc_call(pyfuncitem):
res = yield # <-------------- 生成器方式获取旧结果
new_res = post_process_result(res)
return new_res # <-------------- 生成器方式设置新结果
(左右可以滑动)
可以看到,新式写法简洁了很多,有更强烈的Python风格
04
区分包和目录
新增类:pytest.Directory表示目录
既有类:pytest.Package表示包
包和目录的概念得以加强,各司其职
假设有以下目录关系
myroot/ pytest.ini top/ ├── aaa │ └── test_aaa.py ├── test_a.py ├── test_b │ ├── __init__.py │ └── test_b.py ├── test_c.py └── zzz ├── __init__.py └── test_zzz.py
在此前的 pytest 版本中,被处理为以下结果
<Module top/test_a.py> <Function test_it> <Module top/test_c.py> <Function test_it> <Module top/aaa/test_aaa.py> <Function test_it> <Package test_b> <Module test_b.py> <Function test_it> <Package zzz> <Module test_zzz.py> <Function test_it>
本次更新之后,会被处理为这样
<Dir myroot> <Dir top> <Dir aaa> <Module test_aaa.py> <Function test_it> <Module test_a.py> <Function test_it> <Package test_b> <Module test_b.py> <Function test_it> <Module test_c.py> <Function test_it> <Package zzz> <Module test_zzz.py> <Function test_it>
05
调整用例收集顺序
此前,用例收集顺序是先文件、后目录
此次更新中,用例收集按照字母顺序进行
这一点请macOS用户特别关注,此前因操作系统的差异,macOS中用例执行顺序和Windows中有细微差异。
此次更新可能回到结果造成影响
具体例子可回顾上一小节中收集结果
06
断言警告
pytest 提供了一个对警告进行断言的方式
def test_is_user_waring(): # 出现指定类型警告则测试通过,否则失败 with pytest.warns(UserWarning): warnings.warn(f"这是一条用户警告", UserWarning) warnings.warn(f"这是一条语法警告", SyntaxWarning)
(左右可以滑动)
在此前的版本中执行结果如下(无事发生...)
===================== test session starts ==================platform win32 -- Python 3.12.0, pytest-7.4.0, pluggy-1.0.0rootdir: D:\pytest_7.2.xconfigfile: pytest.inicollected 1 item
test_show_warnings.py . [100%]
===================== 1 passed in 0.01s =====================
(左右可以滑动)
自8.0开始,warns只会捕获它所断言的警告类型,至于其他警告,则会重新抛出,执行结果如下
===================== test session starts ==================platform win32 -- Python 3.12.0, pytest-8.0.0, pluggy-1.4.0rootdir: D:\pytest_8.0.xcollected 1 item
test_show_warnings.py . [100%]
===================== warnings summary ===================== test_show_warnings.py::test_is_user_waring D:\pytest_8.0.x\test_show_warnings.py:10: SyntaxWarning: 这是一条语法警告 warnings.warn(f"这是一条语法警告", SyntaxWarning)
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html============== 1 passed, 1 warning in 0.01s =================
(左右可以滑动)
07
净化 ini 配置的默认值
过去,对于未设置的 ini 配置,会根据类型返回空列表或空字符串,
于是出现了一个 bug:如果设置默认值 None ,会像未设置默认值一样,返回空列表
自8.0开始,对默认值的返回进行了更加具体地处理规则:
如果设置了默认值,返回默认值(哪怕默认值是 None)
没有设置默认值但申明了类型,根据类型返回空列表、空字符串或布尔值 False
没有设置默认,也没有申明类型,返回空字符串
二、 优化改进
01
改进差异显示(diff)
建议安装pygments,增加色彩突出
对于同一个测试用例
def test_diff_list(): a = list('123') b = list('123345')
pytest 7 的执行结果如下
(双击可放大)
更新至8.0后,差异显示更加直观
(双击可放大)
不仅告诉我们错了,还是告诉我们哪里错了;不仅告诉我们哪里错了,还告诉我们为什么错了,具体是哪个字符不对...
还有比这更贴心的测试框架吗?
02
单独控制断言详细程度
如果想要像上一小节中那样显示断言的详细信息,需要添加命令行参数-vv
pytest --vv
需要注意的是:-vv不仅让断言信息更加详细,
也让整个终端输出更加详细,比如会更详细地显示版本信息、用例名、用例执行结果等。
如果只希望断言详细,而不需要其他信息变冗长呢?
8.0新增了一个 ini 配置项目verbosity_assertions,可单独对断言详细程度进行控制
[pytest]verbosity_assertions = 2
03
新式钩子包装器
更加纯粹的,符合生成器风格的钩子包装器,
详见前文 1.3
04
优化日志配置
这个bug 我在前几天读源码的时候发现了,本来准备提交补丁刷个贡献值,
不过已经被人在 9 月份就抢了先。。。
就说说 BUG 的原因:
首先,在 pytest 中对于日志内容有多种处理方式:
caplog_handler:记录到 fixture 中,供用例使用
report_handler:记录到 report 中,供测试报告使用
log_file_handler:记录到文件中,形成日志文件
log_cli_handler:记录到终端中,在命令行输出
然后 pytest 配置文件中有 3 种对日志的配置选项
log_cli_*:作用于终端
log_file_*:作用于文件
log_*:作用于全局
直观上来说,如果log_cli_*或log_file_*没有配置的话,
应该读取log_*中的内容
或者,如果log_cli_*或log_file_*完全相同的话,不必重复配置 2 次,
应该直接对log_*进行配置
没错,代码中也是怎么写的。。。
但是!但是!log_cli_*或log_file_*居然有默!认!值!
就算你真的没有对它们配置,它们也会读取到默认值而不是log_*中的内容,
导致相同的内容必须配置3次才能正常工作
在此次更新中,修复了这个BUG,统一和简化了日志的配置
05
其他
还有一些改进,以文档和类型申明为主,
领取专属 10元无门槛券
私享最新 技术干货