七、Pytest插件开发

目录

  • 插件的加载方式
  • 什么是hook
  • Pytest有哪些hook函数
  • 如何改写hook函数
  • 实战打包、发布

Pytest插件加载方式

  • 外部插件: pip install安装的插件
  • 本地插件:pytest自动模块发现机制(conftest.py存放的)
  • 内置插件:代码内部的_pytest目录加载
    • 内置插件位置:External Libraries-> site-packages-> _pytest-> hookspec.py
      image.png

什么是hook

  • 把大象装冰箱 ,总共分几步
    1. 打开冰箱门
    2. 把大象装进冰箱
    3. 关闭冰箱门
  • 将以上几步封装成函数


    image.png
  • 如果想在“打开冰箱门”前面加一个“把灯打开”,再“把大象装进冰箱前”“拿出一些零食”,就要重新改变代码
  • hook就是将可能发生的接口/功能/操作预留出来,当想进行这些接口/功能/操作时加入到对应位置即可,即按照流程规范运行。
    image.png

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
  1. 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插件
    1. 在当前目录下新建文件conftest.py
    2. 打开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.
        """
    
    1. 将方法复制到 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')
    
    1. 再次执行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就能发现自定义的命令行参数:
    image.png
  • 如果要获取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

实战五:获取环境数据

  • 添加环境数据


    image.png
    • 其中dev下的datas.yaml内容如下:
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 是一个构建工具


    image.png

打包需要两个工具

  • wheel, setuptools
  • setup.py 文件
  • 目录结构


    image.png
  1. 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
)
  1. 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()  # 倒序执行
  1. test_encode.py 文件
import pytest

@pytest.mark.parametrize('name', ['哈利','赫敏'])
def test_chinese(name):
    print(name)
  1. 安装wheel 工具:pip install wheel
  • 打包命令:
    python setup.py sdist bdist_wheel
  • 打包完后


    image.png
  • dist 中上面的是源码包,下面的是whl包,可以通过pip install 进行安装

发布

image.png

你可能感兴趣的:(七、Pytest插件开发)