本文共 8464 字,大约阅读时间需要 28 分钟。
Pytest测试框架功能非常多,它其实就是由一组插件组成的,具有大量的插件,可以通过插件来扩展、定制功能,能满足大部分的测试需求。本文介绍pytest插件的开发方法,帮助更好的理解pytest测试框架。
pytest插件通过hook函数来实现,pytest主要包括以下三种插件
\Lib\site-packages\_pytest\hookspec.py
他们的加载顺序为:
hook函数(钩子函数)是程序中预留的函数(相当于暴露了一个钩子),如果我们需要在程序某个步骤执行某个操作,我们就直接重写特定的钩子函数(挂载到钩子上),这样就实现了我们要增加的功能。没有挂载或者注册钩子时,它就是空的,也就是没有执行任何操作。
Pytest 的hook函数可查看\Lib\site-packages\_pytest\hookspec.py
文件, Pytest hook函数的执行顺序如下():
root└── pytest_cmdline_main ├── pytest_plugin_registered ├── pytest_configure │ └── pytest_plugin_registered ├── pytest_sessionstart │ ├── pytest_plugin_registered │ └── pytest_report_header ├── pytest_collection │ ├── pytest_collectstart │ ├── pytest_make_collect_report │ │ ├── pytest_collect_file │ │ │ └── pytest_pycollect_makemodule │ │ └── pytest_pycollect_makeitem │ │ └── pytest_generate_tests │ │ └── pytest_make_parametrize_id │ ├── pytest_collectreport │ ├── pytest_itemcollected │ ├── pytest_collection_modifyitems │ └── pytest_collection_finish │ └── pytest_report_collectionfinish ├── pytest_runtestloop │ └── pytest_runtest_protocol │ ├── pytest_runtest_logstart │ ├── pytest_runtest_setup │ │ └── pytest_fixture_setup │ ├── pytest_runtest_makereport │ ├── pytest_runtest_logreport │ │ └── pytest_report_teststatus │ ├── pytest_runtest_call │ │ └── pytest_pyfunc_call │ ├── pytest_runtest_teardown │ │ └── pytest_fixture_post_finalizer │ └── pytest_runtest_logfinish ├── pytest_sessionfinish │ └── pytest_terminal_summary └── pytest_unconfigure
我们可以对上面的hook函数进行改写,实现某些功能。我在以前的pytest文章中()介绍了fixture 插件的用法,fixture实现的功能其实也对pytest的hook函数进行了改写,比如pytest_generate_tests,pytest_sessionstart等hook函数,大家如果感兴趣可以查看源码:\Lib\site-packages\_pytest\fixtures.py
前面简要介绍了pytest插件,这些插件是怎么管理的呢?pytest的大量插件使用pluggy进行管理,pluggy是pytest使用的一个插件系统,用于pytest插件的管理和钩子调用。也就是说,pluggy使pytest具有了钩子功能,实现主机(主程序)与插件的连接。
先写一个测试用例test_hook.py:
#!/usr/bin/python3#-*-coding:utf-8-*-import pytest@pytest.mark.parametrize("name",["张三","李四"])def test_name(name): print(name)
执行:
$ pytest -vs test_hook.py::test_name
结果(只截取部分):
test_hook.py::test_name[\u5f20\u4e09] 张三PASSEDtest_hook.py::test_name[\u674e\u56db] 李四PASSED
我们发现用例名字编码格式为Unicode,无法显示中文。
怎么解决呢?我们可以对pytest hook函数pytest_collection_modifyitems()进行重写:
def pytest_collection_modifyitems( session: "Session", config: "Config", items: List["Item"]) -> None: """ called after collection has been performed, may filter or re-order the items in-place. :param _pytest.main.Session session: the pytest session object :param _pytest.config.Config config: pytest config object :param List[_pytest.nodes.Item] items: list of item objects """
根据注释我们知道这个hook函数在用例收集完成后进行调用,可对用例进行过滤或者重新排序(修改用例执行顺序的pytest-ordering插件就修改了这个hook函数)
接下来开始修改这个hook函数,对用例名进行解码并反转用例顺序,在测试用例同级目录下新建conftest.py函数,修改代码如下:
#!/usr/bin/python3#-*-coding:utf-8-*-from typing import Listdef pytest_collection_modifyitems(session, config, items: List): for item in items: item.name = item.name.encode('utf-8').decode('unicode-escape') item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape') items.reverse()
items就是收集到的测试用例,可对它进行各种操作。
再次执行:
$ pytest -vs test_hook.py::test_name
结果(只截取部分):
test_hook.py::test_name[李四] 李四PASSEDtest_hook.py::test_name[张三] 张三PASSED
解码成功,并且顺序反转了。
通过改写hook函数pytest_addoption()可以实现添加自定义的命令行参数,几乎每个pytest 插件都会使用这个hook方法。下面在conftest.py中改写pytest_addoption()方法:
# 添加一个命令行参数def pytest_addoption(parser, pluginmanager): mygroup = parser.getgroup("testgroup") #group将下面所有的 optiongroup都展示在这个group下。 mygroup.addoption("--env", #注册一个命令行选项 default='test', # 参数的默认值 dest = 'env', # 存储的变量 help = 'set your run env' #帮助提示参数的描述信息 )
然后我们在命令行中输入pytest -h
,在打印的帮助信息中,我们可以看到添加的自定义参数:
testgroup: --env=ENV set your run env
接下来获取这个参数,在conftest.py中添加如下代码:
@pytest.fixture(scope='session')def cmdoption(request): env = request.config.getoption("--env", default='test') if env == "test": print("test环境") datapath = "data/test/data.yml" if env == "develop": print("开发环境") datapath = "data/develop/data.yml" with open(datapath) as f: datas = yaml.safe_load(f) return env,datas
测试数据:
编写测试用例:
def test_env(cmdoption): env,datas = cmdoption host = datas['env']['host'] port = datas['env']['port'] url = str(host) + ":" + str(port) print(url)
执行测试用例:
$ pytest -vs test_hook.py::test_env
结果(只截取部分):
test_hook.py::test_env test环境http://192.168.11.1:4567PASSED
传递参数:
$ pytest -vs --env develop test_hook.py::test_env
结果(只截取部分):
test_hook.py::test_env 开发环境http://192.168.0.1:1234PASSED
传递成功
你的Python插件开发完成后,可以对它进行打包发布,方便给别人使用,打包后也可以发布代码到到PyPI上,可参考Python打包文档: ,下面介绍Python打包过程。
pytest-encode/├── LICENSE├── README.md├── setup.py├── pytest_encode/│ └── __init__.py└──tests/ └── test_encode.py
setup.py是一个构建脚本:
import setuptoolssetuptools.setup( name="pytest-encode", # Replace with your own username version="0.0.1", author="hiyongz", author_email="zhiyo2016@163.com@example.com", description="set your encoding", long_description="show Chinese for your mark.parametrize().", url="https://github.com/pypa/sampleproject", project_urls={ "Bug Tracker": "https://github.com/pypa/sampleproject/issues", }, classifiers=[ "Programming Language :: Python :: 3", "Framework :: Pytest", "Topic :: Software Develoment :: Testing", ], license='MIT License', packages=['pytest_encode'], keywords=[ "pytest", "py.test", "pytest_encode", ], install_requires=[ 'pytest' ], python_requires=">=3.6", # 入口模块或者入口函数 entry_points={ 'pytest11':[ 'pytest-encode = pytest_encode' ] }, zip_safe=False,)
$ pip freeze >requirements.txt
__init__.py:
import loggingfrom typing import Listimport pytestlogging.basicConfig(level=logging.INFO, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='report.log', filemode='w' )logger = logging.getLogger(__name__)def pytest_collection_modifyitems(session, config, items: List): for item in items: item.name = item.name.encode('utf-8').decode('unicode-escape') item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape') items.reverse()
打包需要安装两个库:
pip install wheel
pip install setuptools
进入包目录(pytest-encode)下,执行目录:
$ python setup.py sdist bdist_wheel
命令执行完成后,新生成如下文件:
pytest-encode/├── build/ ├── bdist.win-amd64 └── lib └── pytest_encode └── __init__.py├── dist/ ├── pytest-encode-0.0.1.tar.gz └── pytest_encode-0.0.1-py3-none-any.whl └──pytest_encode.egg-info/ ├── dependency_links.txt ├── entry_points.txt ├── not-zip-safe ├── PKG-INFO ├── requires.txt ├── SOURCES.txt └── test_encode.py
pytest-encode-0.0.1.tar.gz为源码包,pytest_encode-0.0.1-py3-none-any.whl可以通过pip install
命令安装
我们新建一个Python虚拟环境,在新的虚拟环境下执行:
$ pip listPackage Version---------- -------pip 21.0.1setuptools 54.2.0
只安装了两个包,下面安装刚才生成的包文件:
$ pip install pytest_encode-0.0.1-py3-none-any.whl
它会自动安装setup.py中定义的依赖包。
然后在新的虚拟环境中编写测试用例并执行:
import pytestfrom pytest_encode import logger@pytest.mark.parametrize("name",["张三","李四"])def test_name(name): logger.info("测试编码") print(name)
$ pytest -vs test_encode.py::test_name
结果(只截取部分):
test_hook.py::test_name[李四] 李四PASSEDtest_hook.py::test_name[张三] 张三PASSED
解码成功,并且生成了日志文件:
report.log:Sat, 03 Apr 2021 20:21:45 test_encode.py[line:13] INFO 测试编码Sat, 03 Apr 2021 20:21:45 test_encode.py[line:13] INFO 测试编码
表明安装的包生效了。
使用Twine来上传包到PyPI,需要注册一个PyPI账号
然后安装twine:
$ pip install twine
上传:
$ twine upload --repository test-encode dist/*
上传过程中会让你输入前面注册的用户名和密码,上传成功后就可以在PyPI上查看
欢迎关注公众号:「测试开发小记」及时接收最新技术文章!
转载地址:http://axouz.baihongyu.com/