python3 pytest (2) - hooks

前言

pytest运行的整个过程中, 充满了各种Hook函数

覆写Hook函数虽然无法改变pytest的执行流程, 但可以实现用户自定义行为

比如collection阶段, 可以不局限于test开头的模块和方法; run阶段, 可以过滤特殊的方法

官网中描述了以下几种hook函数

  • Bootstrapping hooks, 启动pytest时引入
  • Initialization hooks, 初始化插件, 及conftest.py文件
  • Test running hooks, 执行case时调用
  • Collection hooks, 从目录/文件中发现testcase
  • Reporting hooks, 报告相关
  • Debugging/Interaction hooks, 特殊hook函数, 异常交互相关

实例

解析yml文件, 从中提取接口参数, 并执行接口测试

conftest.py文件

import pytest
import sys
import requests

def pytest_collect_file(path, parent):
    """
    定义yml后缀, 且以unit开头的文件, 将被诹
    :param path: 文件全路径
    :param parent: session
    :return: collector
    """
    if path.ext == ".yml" and path.basename.startswith("unit"):
        return YamlFile(path, parent)


class YamlFile(pytest.File):

    def collect(self):
        '''
        collector调用collect方法, 会从文件中提取Item对象, 即testcase
        :return: [Items]
        '''
        import yaml
        raw = yaml.safe_load(self.fspath.open())
        for name, attrs in raw.items():
            yield YamlItem(name, self, attrs)

class YamlItem(pytest.Item):
    def __init__(self, name, parent, attrs):
        super(YamlItem, self).__init__(name, parent)
        self.attrs = attrs

    def setup(self):
        '''
        Item 对象的初始化步骤, 同样还有teardown方法, 这里省略
        :return: 
        '''
        if 'setup' in self.attrs:
            for action, args in self.attrs['setup'].items():
                getattr(sys.modules['builtins'], action)(args)

    def runtest(self):
        '''
        执行Item对象时, 会调用runtest方法
        所以这里定义具体的测试行为
        :return: 
        '''
        try:
            url = self.attrs['body']['url']
            data = self.attrs['body']['data']
            expected = self.attrs['expected']

            resp = requests.get(url, params=data)
            actual = resp.json()

            for key, value in expected.items():
                assert actual['args'][key] == value
        except KeyError:
            raise YamlException(self.name)

class YamlException(Exception):
    pass

unit_get.yml文件

my_get_request:
  body:
    url: https://httpbin.org/get
    data:
      keyword: cucumber
  setup:
    print: setup step
  expected:
    keyword: cucumber

执行pytest即可查看运行结果

相关类介绍

_pytest.config.Parser

解析命令行参数, 以及ini文件, 将结果传递给Config

addoption(self, *opts, **attrs)

group.addoption(
    "--maxfail",
    metavar="num",
    action="store",
    type=int,
    dest="maxfail",
    default=0,
    help="exit after first num failures or errors.",
)

addini(self, name, help, type=None, default=None)

parser.addini(
    "python_files",
    type="args",
    default=["test_*.py", "*_test.py"],
    help="glob-style file patterns for Python test module discovery",
)

_pytest.config.Config

记录命令行的参数option, 以及ini属性

getoption(self, name, default=notset, skip=False)

getini(self, name)

_pytest.main.Session

包含config, testsfailed, testscollected等属性

pytest.Item

即testcase, 继承自Node, 基本属性如下

    def __init__(
        self, name, parent=None, config=None, session=None, fspath=None, nodeid=None
    ):
        #: a unique name within the scope of the parent node
        self.name = name

        #: the parent collector node.
        self.parent = parent

        #: the pytest config object
        self.config = config or parent.config

        #: the session this node is part of
        self.session = session or parent.session

        #: filesystem path where this node was collected from (can be None)
        self.fspath = fspath or getattr(parent, "fspath", None)

        #: keywords/markers collected from all scopes
        self.keywords = NodeKeywords(self)

        #: the marker objects belonging to this node
        self.own_markers = []

        #: allow adding of extra keywords to use for matching
        self.extra_keyword_matches = set()

        # used for storing artificial fixturedefs for direct parametrization
        self._name2pseudofixturedef = {}

Initialization hooks

pytest_addoption(parser)

通过Parser注册命令行参数, 以及添加ini-stype属性

pytest_configure(config)

命令行解析完成后, 初始化conftest.py文件时执行

def pytest_addoption(parser):
    parser.addoption(
        "--season",
        dest="seasion",
        default="summer",
    )

def pytest_configure(config):
    print("season ==> {}".format(config.getoption("seasion")))
===============================
season ==> summer

pytest_unconfigure(config)

测试进程结束前执行

pytest_sessionstart(session)

会话创建时, 尚未收集testcase, 比configure阶段晚

pytest_sessionfinish(session, exitstatus)

整个测试完成后执行

Test running hooks

所有的testcase都被封装成pytest.Item对象

pytest_runtestloop(session)

collection阶段完成后, 启动测试任务时调用

pytest_runtest_logstart(nodeid, location)/pytest_runtest_logfinish(nodeid, location)

执行单条用例, 比pytest_runtest_setup()早

location = (filename, linenum, testname)

pytest_runtest_setup(item)

pytest_runtest_call(item)

pytest_runtest_teardown(item, nextitem)

Collection hooks

pytest采取一定规则, 去特定目录和文件中检索testcase, 而Collection hooks可以定义这些规则

pytest_collection(session)

执行collection操作

pytest_ignore_collect(path, config)

如果返回True, 则忽略掉path路径, 优先执行

pytest_collect_directory(path, parent)

开始扫描目录, 如何处理目录下的文件, 及子目录

pytest_collect_file(path, parent)

汇总测试文件, 作为collection Node, 用于后续提取testcase

pytest_pycollect_makeitem(collector, name, obj)

从已收集的文件中提取出用例, 并封装成item

pytest_generate_tests(metafunc)

向测试方法传递参数, 形成多次调用效果

pytest_collection_modifyitems(session, config, items)

所有的测试用例收集完毕后调用, 可以再次过滤或者对它们重新排序

你可能感兴趣的:(python3 pytest (2) - hooks)