目录
- 插件的加载方式
- 什么是hook
- Pytest有哪些hook函数
- 如何改写hook函数
- 实战打包、发布
Pytest插件加载方式
- 外部插件: pip install安装的插件
- 本地插件:pytest自动模块发现机制(conftest.py存放的)
- 内置插件:代码内部的_pytest目录加载
- 内置插件位置:
External Libraries-> site-packages-> _pytest-> hookspec.py
- 内置插件位置:
什么是hook
- 把大象装冰箱 ,总共分几步
- 打开冰箱门
- 把大象装进冰箱
- 关闭冰箱门
-
将以上几步封装成函数
- 如果想在“打开冰箱门”前面加一个“把灯打开”,再“把大象装进冰箱前”要“拿出一些零食”,就要重新改变代码
- hook就是将可能发生的接口/功能/操作预留出来,当想进行这些接口/功能/操作时加入到对应位置即可,即按照流程规范运行。
Pytest插件
1、site-package/_pytest/hookspec.py
2、https://docs.pytest.org/en/latest/_modules/_pytest/hookspec.html
实战一:编写自己的插件——编码
-
pytest_collection_modifyitems
收集上来的测试用例实现定制化功能 - 解决问题:
- 自定义用例的执行顺序
- 解决编码问题(中文的测试用例名称)
- 自动添加标签
- 含有中文的测试用例名称,改写编码格式:
-
item.name = item.name.encode('utf-8').decode('unicode-escape')
:其中name为测试用例的名字 -
item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')
:其中nodeid为测试用例的路径 _pytest / nodes.py
-
test_chinese.py
代码如下
import pytest
@pytest.mark.parametrize('name', ['哈利','赫敏'])
def test_chinese(name):
print(name)
- 运行结果:其中的用例名没有用中文显示,编码格式为Unicode,不支持中文。需要考虑修改测试用例名字和测试用例路径的编码格式
============================= test session starts =============================
rootdir: D:\Programs\DevOps\Python_Practice\Exercises\pytest_plugins
plugins: allure-pytest-2.8.40
collecting ... collected 2 items
test_chinese.py::test_chinese[\u54c8\u5229]
test_chinese.py::test_chinese[\u8d6b\u654f]
============================== 2 passed in 0.03s ==============================
Process finished with exit code 0
PASSED [ 50%]哈利
PASSED [100%]赫敏
- 修改pytest插件
- 在当前目录下新建文件
conftest.py
- 打开
External Libraries-> site-packages-> _pytest-> hookspec.py
文件,找到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.Session session: The pytest session object. :param _pytest.config.Config config: The pytest config object. :param List[pytest.Item] items: List of item objects. """
- 将方法复制到
conftest.py
文件下进行改写
def pytest_collection_modifyitems(ession, config, items): for item in items: item.name = item.name.encode('utf-8').decode('unicode-escape') item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')
- 再次执行
test_chinese.py
结果如下:发现用例名改为了中文
- 在当前目录下新建文件
rootdir: D:\Programs\DevOps\Python_Practice\Exercises\pytest_plugins
plugins: allure-pytest-2.8.40
collecting ... collected 2 items
test_chinese.py::test_chinese[哈利] PASSED [ 50%]哈利
test_chinese.py::test_chinese[赫敏] PASSED [100%]赫敏
============================== 2 passed in 0.02s ==============================
Process finished with exit code 0
实战二:倒序执行用例
- 由于
def pytest_collection_modifyitems(session, config, items:List):
中items为用例列表,所以可以对其进行列表的方法,比如.reverse()
进行倒序执行 - 在
conftest.py
文件中加入items.reverse()
from typing import List
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 就是所有用例列表
items.reverse() # 倒序执行
- 再次执行
test_chinese.py
结果如下:发现用例执行顺序改变
rootdir: D:\Programs\DevOps\Python_Practice\Exercises\pytest_plugins
plugins: allure-pytest-2.8.40
collecting ... collected 2 items
test_chinese.py::test_chinese[赫敏] PASSED [ 50%]赫敏
test_chinese.py::test_chinese[哈利] PASSED [100%]哈利
============================== 2 passed in 0.02s ==============================
Process finished with exit code 0
实战三:只执行打了标签的测试用例
- 测试代码如下,其中有两条用例名包含
login
:
import pytest
@pytest.mark.parametrize('name', ['哈利','赫敏'])
def test_chinese(name):
print(name)
def test_login():
print("login")
def test_login_fail():
print("login fail")
assert False
def test_search():
print("search")
- 修改
conftest.py
文件,给login
打上标签
from typing import List
import pytest
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')
# 如果login在测试用例路径中,则对其打标签
if "login" in item.nodeid:
item.add_marker(pytest.mark.login)
# 修改用例执行顺序,其中 items 就是所有用例列表
items.reverse() # 倒序执行
- 在Terminal中执行
pytest -m login -vs
其中-m
为运行指定标签的用例,后面跟上标签名,-v
为打印详细信息,-s
用于显示测试函数中print()函数输出。执行结果如下,可以看到只执行了login标签的用例
(venv) D:\Programs\DevOps\Python_Practice\Exercises\pytest_plugins>pytest -m login -vs
============================================================================================= test session starts =============================================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- d:\programs\devops\python_practice\venv\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\Programs\DevOps\Python_Practice\Exercises\pytest_plugins
plugins: allure-pytest-2.8.40
collected 5 items / 3 deselected / 2 selected
test_chinese.py::test_login_fail login fail
FAILED
test_chinese.py::test_login login
PASSED
================================================================================================== FAILURES ===================================================================================================
_______________________________________________________________________________________________ test_login_fail _______________________________________________________________________________________________
def test_login_fail():
print("login fail")
> assert False
E assert False
test_chinese.py:17: AssertionError
============================================================================================== warnings summary ===============================================================================================
conftest.py:17
D:\Programs\DevOps\Python_Practice\Exercises\pytest_plugins\conftest.py:17: PytestUnknownMarkWarning: Unknown pytest.mark.login - is this a typo? You can register custom marks to avoid this warning - for d
etails, see https://docs.pytest.org/en/stable/mark.html
item.add_marker(pytest.mark.login)
-- Docs: https://docs.pytest.org/en/stable/warnings.html
=========================================================================================== short test summary info ===========================================================================================
FAILED test_chinese.py::test_login_fail - assert False
============================================================================ 1 failed, 1 passed, 3 deselected, 1 warning in 0.23s =============================================================================
实战四:添加命令行参数
def pytest_addoption(parser):
mygroup = parser.getgroup("hogwarts") #group将下面所有的option都展示在这个group下。
mygroup.addoption("--env", #注册一个命令行选项
default='test', #参数的默认值
dest='env' ,#存储的变量
help='set your run env' #帮助提示参数的描述信息
)
- 如何针对传入的不同参数完成不同的逻辑处理?创建一fixture
@pytest.fixture(scope='session')
def cmdoption(request):
return request.config.getoption("--env", default='test')
- 在terminal中执行
pytest --help
就能发现自定义的命令行参数:
- 如果要获取addoption中定义的命令行参数,可以在
conftest.py
中定义fixture如下:
# 定义fixture从而获取addoption里面函数
@pytest.fixture(scope='session')
def cmdoption(request):
env = request.config.getoption("--env", default='test')
if env == 'test':
print("这是测试环境")
elif env == 'dev':
print("这是开发环境")
- 新建一个测试用例如下,将在conftest中定义的fixture函数名传过来
# 将在conftest中定义的fixture函数传过来
def test_env(cmdoption):
print(cmdoption)
- 执行结果如下:
test_chinese.py::test_env 这是测试环境
PASSED [100%]test
============================== 1 passed in 0.04s ==============================
Process finished with exit code 0
- 如果要修改环境为开发环境,则在Terminal中执行
pytest --env dev test_chinese.py::test_env -vs
,执行结果如下:
rootdir: D:\Programs\DevOps\Python_Practice\Exercises\pytest_plugins
plugins: allure-pytest-2.8.40
collected 1 item
test_chinese.py::test_env 这是开发环境
dev
PASSED
实战五:获取环境数据
-
添加环境数据
- 其中dev下的
datas.yaml
内容如下:
- 其中dev下的
env:
host: https://www.baidu.com
port: 443
- 其中test下的
datas.yaml
内容如下:
env:
host: http://www.baidu.com
port: 80
- 修改
conftest.py
文件如下:主要修改cmdoption函数
from typing import List
import pytest
import yaml
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')
# 如果login在测试用例路径中,则对其打标签
if "login" in item.nodeid:
item.add_marker(pytest.mark.login)
# 修改用例执行顺序,其中 items 就是所有用例列表
items.reverse() # 倒序执行
# 添加一个命令行参数
def pytest_addoption(parser):
mygroup = parser.getgroup("hogwarts") #group将下面所有的option都展示在这个group下。
mygroup.addoption("--env", #注册一个命令行选项
default='test', #参数的默认值
dest='env' ,#存储的变量
help='set your run env' #帮助提示参数的描述信息
)
# 定义fixture从而获取addoption里面函数
@pytest.fixture(scope='session')
def cmdoption(request):
env = request.config.getoption("--env", default='test')
if env == 'test':
print("这是测试环境")
datapath = "./datas/test/datas.yml"
elif env == 'dev':
print("这是开发环境")
datapath = "./datas/dev/datas.yml"
with open(datapath) as f:
datas = yaml.safe_load(f)
return env, datas
- 修改测试用例如下:
# 将在conftest中定义的fixture函数传过来
def test_env(cmdoption):
env, datas = cmdoption
print(datas)
host = datas['env']['host']
port = datas['env']['port']
url = str(host) + ":" + str(port)
print(url)
- 测试结果:
plugins: allure-pytest-2.8.40
collected 1 item
test_chinese.py::test_env 这是开发环境
{'env': {'host': 'https://www.baidu.com', 'port': 443}}
https://www.baidu.com:443
PASSED
打包发布
- 打包必须要有代码和setup.py 文件
-
setup.py 是一个构建工具
打包需要两个工具
- wheel, setuptools
- setup.py 文件
-
目录结构
- setup.py 文件:
from setuptools import setup
setup(
name='pytest_encode',
url='https://github.com/xxx/pytest-encode',
version='1.0',
author="loafer",
author_email='[email protected]',
description='set your encoding and logger',
long_description='Show Chinese for your mark.parametrize(). Define logger variable for getting your log',
classifiers=[# 分类索引 ,pip 对所属包的分类
'Framework :: Pytest',
'Programming Language :: Python',
'Topic :: Software Development :: Testing',
'Programming Language :: Python :: 3.8',
],
license='proprietary',
packages=['pytest_encode'],
keywords=[
'pytest', 'py.test', 'pytest_encode',
],
# 需要安装的依赖
install_requires=[
'pytest'
],
# 入口模块 或者入口函数
entry_points={
'pytest11': [
'pytest-encode = pytest_encode',
]
},
zip_safe=False
)
- pytest_encode 中的 _init_.py 文件
from typing import List
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 就是所有用例列表
items.reverse() # 倒序执行
- test_encode.py 文件
import pytest
@pytest.mark.parametrize('name', ['哈利','赫敏'])
def test_chinese(name):
print(name)
- 安装wheel 工具:
pip install wheel
- 打包命令:
python setup.py sdist bdist_wheel
-
打包完后
- dist 中上面的是源码包,下面的是whl包,可以通过pip install 进行安装