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