pytest模块测试开发必备技能-conftest.py重写hookspec方法

pytest的conftest可以重写模块的hook方法,通过,便于进行二次开发扩展,通过文档的学习很容易理解。

构建一个简单的测试脚本

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import os
import pytest

BASE_DIR = os.path.dirname(__file__)
ids = ["jia","jian","cheng","chu"]
testcase = {
"jia":[1,"+",1,2],
"jian":[1,"-",1,0],
"cheng":[1,"*",0,0],
"chu":[1,"/",0,1]
}

class TestHook:
    @pytest.mark.parametrize('stepdata', [testcase['jia']], ids=['jia'])
    def test_01_jia(self, stepdata):
        a,how,b,exp = stepdata
        assert eval(f"{a}{how}{b}=={exp}") == True

    @pytest.mark.parametrize('stepdata', [testcase['jian']], ids=['jian'])
    def test_02_jian(self, stepdata):
        a, how, b, exp = stepdata
        assert eval(f"{a}{how}{b}=={exp}") == True

    @pytest.mark.parametrize('stepdata', [testcase['cheng']], ids=['cheng'])
    def test_03_cheng(self, stepdata):
        a, how, b, exp = stepdata
        assert eval(f"{a}{how}{b}=={exp}") == True

    @pytest.mark.parametrize('stepdata', [testcase['chu']], ids=['chu'])
    def test_04_chu(self, stepdata):
        a, how, b, exp = stepdata
        assert eval(f"{a}{how}{b}=={exp}") == True

if __name__ == '__main__':
    pytest.main(args=['-s',  __file__])

pytest.ini

[pytest]
addopts = --disable-warnings --alluredir=./report/data
log_level = INFO
log_format = %(levelname)-8s%(asctime)s%(name)s:%(filename)s:%(lineno)d %(funcName)s %(message)s
log_date_format = %d-%m-%y %H:%M:%S
log_cli = true
log_cli_level = INFO
log-file = ./test.log
log-file-level = DEBUG
enable_assertion_pass_hook = true

采集测试用例相关函数

@pytest.hookimpl(hookwrapper=True)
def pytest_collection(session:Session):
    '''
    为给定会话执行收集阶段。
在第一个非None结果处停止,请参见:ref:`firstresult`。
不使用返回值,而只是停止进一步处理。
默认的收集阶段是这样的(有关完整的详细信息,请参阅各个挂钩):
1.从`session``开始作为初始收集器:
1.`pytest_collectstart(收集器)``
2.``report=pytest_make_collect_report(收集器)``
3.`pytest_exception_interact(收集器、调用、报告)``如果发生交互异常
4.对于每个收集的节点:
1.如果是项,``pytest_itemcollected(项)``
2.如果是收集器,则递归到其中。
5.`pytest_collectreport(报表)``
2.``pytest_collection_modifyitems(会话、配置、项)``
1.“pytest_deselected(items)”用于任何取消选择的项(可以调用多次)
3.`pytest_collection_finish(会话)``
4.将“session.items”设置为已收集项目的列表
5.将`session.testscollected``设置为已收集的项目数
您可以实现这个钩子,以便在收集之前只执行一些操作,
例如,终端插件使用它来开始显示集合
计数器(并返回“无”)。
:param pytest。会话会话:pytest会话对象。
    :param session:
    :return:
    '''
    logging.info(session)
    logging.info("\n")
    yield
    logging.info(session)
    logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_collectstart(collector:Collector):
    """
    Collector starts collecting
    :param collector(Node):
    收集器实例通过collect()创建子级,从而
迭代地构建一棵树。
    Node:
    测试组件收集器和项的基类集合树。收集器子类有子类;项是叶节点。
    :param name:父节点范围内的唯一名称。
    :param parent:父收集器节点。
    :param config:
    :param session:
    :param fspath:
    :param path:从中收集此节点的文件系统路径(可以是None)。
    :param nodeid:self._nodeid = self.parent.nodeid + "::" + self.name
    :return:
    """
    for attr in collector.__slots__:
        logging.info({attr:getattr(collector, attr)})
    logging.info("\n")
    yield
    for attr in collector.__slots__:
        logging.info({attr:getattr(collector, attr)})
    logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_make_collect_report(collector:Collector) -> CollectReport:
    """
    执行:func:`collector.collective()<pytest.collector.collective>`并返回
a:class:`~pytest.CollectReport`。
在第一个非None结果处停止,请参见:ref:`firstresult`。
    :param collector:
    :return:
    集合报表对象。报表可以包含任意的额外属性。
    #:规范化的集合节点ID。
    self.nodeid=节点ID
    #:测试结果,始终为“通过”、“失败”和“跳过”之一。
    self.outcome=结果
    #:无或故障表示。
    self.longrepr=长代表
    #:收集的项目和收集节点。
    self.result=结果或[]
    #:带有额外信息的字符串``(标题、内容)``的元组
    #:用于测试报告。pytest用于添加捕获的文本
    #:来自“stdout”、“stderr”和截获的日志事件。也许
    #:被其他插件用来向报表中添加任意信息。
    self.sections=列表(节)
    """
    for attr in collector.__slots__:
        logging.info({attr:getattr(collector, attr)})
    logging.info("\n")
    result = yield
    logging.info(f"sections:{result._result}")
    logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makemodule(module_path:Path, path) -> Optional["Module"]:
    """
    “”“对于给定的路径,返回Module收集器或None。
将为每个匹配的测试模块路径调用此钩子。
如果需要,则需要使用pytest_collect_file挂钩
为不匹配的文件创建测试模块作为测试模块。
在第一个非None结果处停止,请参见:ref:`firstresult`。
:param pathlib。Path module_Path:要收集的模块的路径。
:param LEGACY_PATH PATH:要收集的模块的路径(已弃用)。
..版本已更改::7.0.0
“module_path”参数添加为:class:`pathlib.path`
相当于`path`参数。
“path”参数已被弃用,取而代之的是“fspath”参数。
    :param module_path:
    :param path:
    :return:
    """
    logging.info(f"module_path:{module_path},path:{path}")
    logging.info("\n")
    result = yield
    logging.info(f'result:{result._result}')
    logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_generate_tests(metafunc:Metafunc):
    """
    Generate (multiple) parametrized calls to a test function
    :param metafunc:
    parametrize:
    “”“使用列表添加对底层测试函数的新调用
给定参数名称的个参数值。执行参数化
在收集阶段。如果您需要设置昂贵的资源
请参阅有关将间接设置为执行此操作,而不是在测试设置时执行此操作。
可以被调用多次,在这种情况下,每次调用都会参数化所有
以前的参数化,例如。
::
未帧化:t
参数化[“x”,“y”]:t[x],t[y]
参数化[1,2]:t[x-1],t[x-2],t[y-1],t[i-2]
:param参数名称:
逗号分隔的字符串,表示一个或多个参数名称,或
参数字符串的列表/元组。
:param参数值:
参数值列表决定调用测试的频率
不同的参数值。
如果只指定了一个argname,则argvalues是一个值列表。
如果指定了N个参数名称,则参数值必须是
N元组,其中每个元组元素为其指定一个值
各自的argname。
:param间接:
参数名称的列表(参数名称的子集)或布尔值。
如果为True,则列表包含argname中的所有名称。每个
与此列表中的argname相对应的argvalue将
作为request.param传递到其各自的argname fixture
功能,以便在
测试的设置阶段,而不是收集时。
:param ID:
“argvalues”的id序列(或生成器),
或可调用以返回每个argvalue的部分id。
使用序列(以及诸如“itertools.count()”之类的生成器)
返回的id应为“string”、“int”、“float”类型,
``bool ``或``无``。
它们被映射到“argvalues”中的相应索引。
``无``表示使用自动生成的id。
如果它是可调用的,则将为中的每个条目调用它
``argvalues“”,返回值用作
自动生成的整套id(其中零件与
破折号(“-”))。
这有助于为某些项目提供更具体的ID,例如。
日期。返回`None``将使用自动生成的id。
如果没有提供ID,则将从
参数值。
:param作用域:
如果指定,则表示参数的范围。
作用域用于按参数实例对测试进行分组。
它还将覆盖任何夹具功能定义的范围,允许
使用测试上下文或配置设置动态范围。
    :return:
    """
    logging.info(metafunc)
    logging.info("\n")
    yield
    logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_collectreport(report:CollectReport):
    """
    Collector finished collecting
    :param report:
    :return:
    """
    logging.info("nodeid:"+report.nodeid)
    logging.info("outcome:"+report.outcome)
    logging.info(f"longrepr:{report.longrepr}")
    logging.info(f"result:{report.result}")
    logging.info(f"sections:{report.sections}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_collection_modifyitems(session:Session, config:Config, items:List["Item"]):
    """
    Called after collection has been performed. May filter or re-order
    the items in-place.
    :param session:
    :param config:
    :param items:
    :return:
    """
    logging.info(items)
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_collection_finish(session:Session):
    """Called after collection has been performed and modified."""
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_report_collectionfinish(config,start_path, startdir, items) :
    """
    返回要在收集后显示的字符串或字符串列表
已成功完成。
这些字符串将显示在标准的“收集的X个项目”消息之后。
..版本已添加::3.2
:param pytest.Config-Config:pytest配置对象。
:param Path start_Path:启动目录。
:param LEGACY_PATH startdir:启动目录(已弃用)。
:param items:将要执行的pytest项的列表;不应修改此列表。
..注意:
插件返回的行显示在那些插件之前
跑在它前面。
如果要先显示行,请使用
:ref:`trylast=True<plugin-hookdorder>`。
..版本已更改::7.0.0
“start_path”参数添加为:class:`pathlib.path`
相当于“startdir”参数。`startdir``参数
已被弃用。
    :param config:
    :param startdir:
    :param items:
    :return:
    """
    logging.info(f"start_path:{start_path}")
    logging.info(f"items:{items}")
    logging.info("\n")
    result = yield
    logging.info(f"result:{result._result}")
    logging.info("\n")

运行测试用例相关函数

@pytest.hookimpl(hookwrapper=True)
def pytest_runtestloop(session:Session):
    """
    执行主运行时测试循环(在收集完成后)。
默认的钩子实现为所有项执行运行时协议
在会话中收集(`session.items``),除非收集失败
或者设置了“collectally”pytest选项。
如果在任何时候调用:py:func:`pytest.exit`,则循环为
立即终止。
如果在任何时候设置了“session.sshould fail”或“session.should stop”,则
在当前项的运行时测试协议完成后,循环终止。
:param pytest。会话会话:pytest会话对象。
在第一个非None结果处停止,请参见:ref:`firstresult`。
不使用返回值,而只是停止进一步处理。
    :param session:
    :return:
    """
    logging.info(f"items:{session.items}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item:Item,nextitem:Optional[Item]):
    """
    对单个测试项目执行运行时测试协议。
默认的运行时测试协议是这样的(有关详细信息,请参阅各个挂钩):
-``pytest_runtest_logstart(节点ID,位置)``
-设置阶段:
-``call=pytest_runtest_setup(项)``(包装在``CallInfo(when=“setup”)``中)
-`report=pytest_runtest_makereport(项目,调用)``
-``pytest_runtest_logreport(报表)``
-`pytest_exception_interact(调用,报告)``如果发生交互式异常
-调用阶段,如果设置已通过,并且未设置“setuponly”pytest选项:
-``call=pytest_runtest_call(项)``(包装在``CallInfo(when=“call”)``中)
-`report=pytest_runtest_makereport(项目,调用)``
-``pytest_runtest_logreport(报表)``
-`pytest_exception_interact(调用,报告)``如果发生交互式异常
-拆卸阶段:
-``call=pytest_runtest_teardown(项,nexttitem)``(包装在``CallInfo(when=“teardown”)``中)
-`report=pytest_runtest_makereport(项目,调用)``
-``pytest_runtest_logreport(报表)``
-`pytest_exception_interact(调用,报告)``如果发生交互式异常
-``pytest_runtest_logfinish(节点ID,位置)``
:param item:为其执行运行时协议的测试项。
:param nexttitem:计划成为下一个测试项目(如果这是结束我的朋友,则为None)。
在第一个非None结果处停止,请参见:ref:`firstresult`。
不使用返回值,而只是停止进一步处理。
    :param item:
    :param nextitem:
    :return:
    """
    logging.info(f"item{item}")
    logging.info(f"item{nextitem}")
    logging.info("\n")
    yield

# 设置阶段
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_logstart(nodeid,location):
    logging.info(f"nodeid:{nodeid}")
    logging.info(f"location:{location}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(item):
    """
    调用以执行测试项的设置阶段。
默认实现在“item”及其所有
家长(尚未设置)。这包括获得
项目所需固定装置的值(尚未获得
然而)
    :param item:
    :return:
    """
    logging.info(f"item:{item}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
    """
    “调用以运行测试项的测试(调用阶段)。
默认实现调用`item.runtest()``。
    :param item:
    :return:
    """
    logging.info(f"item:{item}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_teardown(item:Item,nextitem):
    """
    调用以执行测试项的拆卸阶段。
默认实现运行终结器并调用`teardown()``
关于“项目”及其所有父项(需要删除)。这
包括运行项目所需固定装置的拆卸阶段(如果
它们超出范围)。
:param nextitem:
计划成为下一个测试项目(如果没有进一步的测试项目,则无
计划)。此参数用于执行精确的拆卸,即。
只调用足够的终结器,以便nextim只需要调用
设置功能。
    :param item:
    :return:
    """
    logging.info(f"item:{item}")
    logging.info(f"nextitem:{nextitem}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item:Item,call:CallInfo) -> TestReport:
    """
    调用以为以下各项创建一个:class:`~pytest.TestReport`
测试项目的设置、调用和拆卸运行时测试阶段。
请参阅:hook:`pytest_runtest_protocol`以获取运行时协议的描述。
:param call:阶段的:class:`~pytest.CallInfo`。
在第一个非None结果处停止,请参见:ref:`firstresult`。
    :param item:
    :param call:
    :return:
    """
    logging.info(f"item:{item}")
    logging.info(f"call:{call}")
    logging.info("\n")
    result = yield
    # logging.info(f"result{result._result}")
    # logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_logreport(report:TestReport):
    """
    处理为每个生成的:class:`~pytest.TestReport`
项目的设置、调用和拆卸运行时阶段。
请参阅:hook:`pytest_runtest_protocol`以获取运行时协议的描述。
    :param report:
    :return:
    """
    logging.info(f"report:{report}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_exception_interact(node:Union[Item, Collector],call:CallInfo, report:Union[CollectReport, TestReport]):
    """
    当引发异常时调用,该异常可能是
交互处理。
可能在收集期间调用(请参见:hook:`pytest_make_collect_report`),
在这种情况下,“report”是一个:class:“CollectReport”。
可以在项目的运行测试期间调用(请参见:hook:`pytest_runtest_procol`),
在这种情况下,“report”是一个:class:“TestReport”。
如果引发的异常是内部异常,则不会调用此钩子
类似于`skip.exception``的异常。
    :param call:
    :param report:
    :return:
    """
    logging.info(f"node:{node}")
    logging.info(f"call:{call}")
    logging.info(f"report:{report}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_logfinish(nodeid,location):
    logging.info(f"nodeid:{nodeid}")
    logging.info(f"location:{location}")
    logging.info("\n")
    yield


@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_post_finalizer(fixturedef:FixtureDef,request:SubRequest):
    """
    在fixture拆卸之后,但在缓存被清除之前调用,因此
fixture结果“fixturedef.cached_result”仍然可用(不可用
``无``)。
    :param fixturedef:
    :param request:
    :return:
    """
    if request.fixturename == "stepdata":
        with allure.step("测试数据"):
            allure.attach(json.dumps(fixturedef.cached_result, ensure_ascii=False, indent=4), '测试数据:',
                          allure.attachment_type.JSON)
    logging.info(f"fixturedef:{fixturedef}")
    logging.info(f"cached_result:{fixturedef.cached_result}")
    logging.info(f"request:{request}")
    logging.info("\n")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_setup(fixturedef,request):
    """
    执行夹具设置。
:return:对fixture函数的调用的返回值。
在第一个非None结果处停止,请参见:ref:`firstresult`。
..注意:
如果fixture函数返回None,则的其他实现
根据
:ref:`firstresult`选项的行为。
    :param fixturedef:
    :param request:
    :return:
    """
    logging.info(f"fixturedef:{fixturedef}")
    logging.info(f"cached_result:{fixturedef.cached_result}")
    logging.info(f"request:{request}")
    logging.info("\n")
    result = yield
    logging.info(f"result:{result._result}")
    logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem:Module):
    """
    Call underlying test function.
    Stops at first non-None result, see :ref:`firstresult`.
    :param pyfuncitem:
    :return:
    """
    for attr in pyfuncitem.__slots__:
        logging.info({attr:getattr(pyfuncitem, attr)})
    logging.info(f"pyfuncitem{pyfuncitem}")
    result = yield # True or None
    logging.info(f"result:{result._result}")
    logging.info("\n")

生成测试报告相关函数

@pytest.hookimpl(hookwrapper=True)
def pytest_report_teststatus(report, config):
    """
    返回状态的结果类别、简短字母和详细单词报告。
结果类别是对结果进行计数的类别
例如“passed”、“skipped”、“error”或空字符串。
短字母会随着测试的进行而显示,例如“.”、“s”、,
“E”或空字符串。
在详细模式下进行测试时,会显示详细单词,例如
例如“PASSED”、“SKIPPED”、“ERROR”或空字符串。
pytest可以根据报告结果隐式地设置这些样式。
要提供显式样式,请为详细单词返回一个元组,
例如``“重新运行”,“R”,(“rerun”,{“yellow”:True})``。
:param report:要返回其状态的报表对象。
:param config:pytest配置对象。
在第一个非None结果处停止,请参见:ref:`firstresult`。
    :param report:
    :param config:
    :return:
    """
    logging.info(f"report:{report}")
    logging.info(f"report_json:{pytest_report_to_serializable(report)}")
    """Serialize the given report object into a data structure suitable for
        sending over the wire, e.g. converted to JSON."""
    logging.info("\n")
    result = yield
    logging.info(f"result:{result._result}")
    logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_assertrepr_compare(config,op,left,right):
    """
    返回失败断言表达式中的比较说明。
返回None表示没有自定义解释,否则返回列表
字符串。字符串将由换行符连接,但任何换行符
*在*中,将转义一个字符串。请注意,除第一行外的所有行
稍微缩进,目的是让第一行成为摘要。
:param pytest.Config-Config:pytest配置对象。
    :param config:
    :param op:
    :param left:
    :param right:
    :return:
    """
    with allure.step("开始断言"):
        allure.attach(f"left:{left}\nop:{op}\nright:{right}", "参数:",allure.attachment_type.TEXT)
        logging.info('开始断言' + str(left) + str(op) + str(right))
        logging.info("\n")
    result = yield
    with allure.step("断言结果"):
        allure.attach(str(result.get_result()), "断言结果为:",allure.attachment_type.TEXT)
        logging.info('断言结果为:' + str(result.get_result()))
        logging.info("\n")

@pytest.hookimpl(hookwrapper=True)
def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> None:
    """
    每当断言通过时调用。
..版本已添加::5.0
使用这个钩子在传递断言之后进行一些处理。
原始断言信息在“orig”字符串中可用
并且pytest内省的断言信息在
`解释字符串。
此挂钩必须由`enable_assertion_Pss_hook显式启用``
ini文件选项:
..代码块:ini
[比重测试]
enable_asseration_pass_hook=真
您需要**清理项目目录和解释器库中的.pyc**文件
启用此选项时,as断言将需要重新编写。
:param pytest。项目项:当前测试的pytest项目对象。
:param int lineno:断言语句的行号。
:param str orig:具有原始断言的字符串。
:param str expl:带有断言解释的字符串。
    :param item:
    :param lineno:
    :param orig:
    :param expl:
    :return:
    """
    with allure.step("pytest_assertion_pass"):
        allure.attach(f"item:{item}\nlineno:{lineno}\norig:{orig}\nexpl:{expl}","参数:",allure.attachment_type.TEXT)
        logging.info(f"item:{item}")
        logging.info(f"lineno:{lineno}")
        logging.info(f"orig:{orig}")
        logging.info(f"expl:{expl}")
    yield

@pytest.hookimpl(hookwrapper=True)
def pytest_terminal_summary(terminalreporter:TerminalReporter,exitstatus:ExitCode,config):
    """
    在终端摘要报告中添加一个部分。
:param_pytest.terminal.TerminalReporter终端报告程序:内部终端报告程序对象。
:param int exitstatus:将报告回操作系统的退出状态。
:param pytest.Config-Config:pytest配置对象。
    :param terminalreporter:
    KNOWN_TYPES = (
    "failed",
    "passed",
    "skipped",
    "deselected",
    "xfailed",
    "xpassed",
    "warnings",
    "error",
)
    :param exitstatus:
    :param config:
    :return:
    """
    for type in (
    "failed",
    "passed",
    "skipped",
    "deselected",
    "xfailed",
    "xpassed",
    "warnings",
    "error",
):
        logging.info(f'{type}:{terminalreporter.getreports(type)}')
    logging.info(f'exitstatus:{exitstatus.__repr__()}')
    """
    #: Tests passed.
    OK = 0
    #: Tests failed.
    TESTS_FAILED = 1
    #: pytest was interrupted.
    INTERRUPTED = 2
    #: An internal error got in the way.
    INTERNAL_ERROR = 3
    #: pytest was misused.
    USAGE_ERROR = 4
    #: pytest couldn't find tests.
    NO_TESTS_COLLECTED = 5
    """
    yield
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值