• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

pytest框架二次开发:机器人报警

武飞扬头像
做测试的喵酱
帮助1

目录

一、背景:

二、实现思路:

2.1 报警接口

2.2 、HOOK函数:

2.2.1 pytest_runtest_makereport

2.2.2 pytest_collectstart

2.2.3 pytest_collectreport

三、项目实战:

3.1 我们先实现报警工具

3.2 跨模块的全局变量

3.3 通过HOOK函数收集报警信息&报警


一、背景:

我想要实现的效果,当接口自动化case运行失败时,触发企业微信机器人报警,艾特相关人员,及发送失败case的相关信息。

报警信息包括:case等级、case描述、case名称、case的开发人员。

二、实现思路:

2.1 报警接口

我们要通过企业微信实现。企业微信群聊机器人代码示例分享 - 开发者社区 - 企业微信开发者中心

 可以去查看官方文档。

case运行时,我们可以对报警做一个筛选,哪些级别的case报警,哪些级别的case不报警。或者可以再扩展一下其他逻辑。

模版:

  1.  
    requests.post('https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的机器人的key',
  2.  
    headers={'Content-Type': 'application/json'},data=json.dumps({'msgtype': 'text','text':{'mentioned_mobile_list': '你要艾特的人手机号列表','content': '{}'.format(msg).encode('utf-8').decode(
  3.  
    "latin1")}}, ensure_ascii=False))

注意:

1、艾特人,可以通过uid,也可以通过手机号,我们这里通过手机号 'mentioned_mobile_list',

是一个list,如

'mentioned_mobile_list':["1871871817817",]

2、 内容为中文的话,需要注意编码。

.encode('utf-8').decode("latin1")


 

2.2 、HOOK函数:

我们需要收集一共执行了多少case、失败了多少case、每条case的用例级别、用例描述、作者、失败原因等。

实现这些数据的收集,我们需要使用pytest 提供的Hook函数。

官方文档:

Writing hook functions — pytest documentation

API Reference — pytest documentation

HOOK函数:

1、是个函数,在系统消息触发时别系统调用

2、不是用户自己触发的

3、使用时直接编写函数体

4、钩子函数的名称是确定,当系统消息触发,自动会调用

HOOK也被称作钩子。他是系统或者第三方插件暴露的一种可被回调的函数

pytest具体提供了哪些hook函数,可以在\venv\Lib\site-packages\_pytest>hookspec.py文件中查看,里面每一个钩子函数都有相应的介绍。

插件就是用1个或者多个hook函数。如果想要编写新的插件,或者是仅仅改进现有的插件,都必须通过这个hook函数进行。所以想掌握pytest插件二次开发,必须搞定hook函数。

Hook函数都在这个路径下:site-packages/_pytest/hookspec.py

我们看一下hookspec.py源码,看一看pytest这个框架都给我们提供了哪些回调函数。

2.2.1 pytest_runtest_makereport

pytest框架,提供了pytest_runtest_makereport回调函数,来获取用例的执行结果。

官方文档:

API Reference — pytest documentation

  1.  
    @hookspec(firstresult=True)
  2.  
    def pytest_runtest_makereport(
  3.  
    item: "Item", call: "CallInfo[None]"
  4.  
    ) -> Optional["TestReport"]:
  5.  
    """Called to create a :py:class:`_pytest.reports.TestReport` for each of
  6.  
    the setup, call and teardown runtest phases of a test item.
  7.  
     
  8.  
    See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
  9.  
     
  10.  
    :param CallInfo[None] call: The ``CallInfo`` for the phase.
  11.  
     
  12.  
    Stops at first non-None result, see :ref:`firstresult`.
  13.  
    """

(python好像没有抽象类与接口),这里框架提供了一个pytest_runtest_makereport函数模版,我们需要去实现它,实现它的内部逻辑。

函数名称:pytest_runtest_makereport

传参:

---Item类的实例item,测试用例。

---CallInfo[None]类的实例call

返回值:Optional["TestReport"]

TestReport类的实例,或者为None(Optional更优雅的处理none)

备注:

Optional介绍:

每个case的执行过程,其实都是分三步的,1 setup初始化数据 2执行case 3teardown

这里的item是测试用例,call是测试步骤,具体执行过程如下:

*先执行when="setup"返回setup的执行结果

*再执行when="call"返回call的执行结果

*最后执行when="teardown"返回teardown的执行结果。

查看一下返回结果:TestReport源码

  1.  
    @final
  2.  
    class TestReport(BaseReport):
  3.  
    """Basic test report object (also used for setup and teardown calls if
  4.  
    they fail)."""
  5.  
     
  6.  
    __test__ = False
  7.  
     
  8.  
    def __init__(
  9.  
    self,
  10.  
    nodeid: str,
  11.  
    location: Tuple[str, Optional[int], str],
  12.  
    keywords,
  13.  
    outcome: "Literal['passed', 'failed', 'skipped']",
  14.  
    longrepr: Union[
  15.  
    None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
  16.  
    ],
  17.  
    when: "Literal['setup', 'call', 'teardown']",
  18.  
    sections: Iterable[Tuple[str, str]] = (),
  19.  
    duration: float = 0,
  20.  
    user_properties: Optional[Iterable[Tuple[str, object]]] = None,
  21.  
    **extra,
  22.  
    ) -> None:
  23.  
    #: Normalized collection nodeid.
  24.  
    self.nodeid = nodeid
学新通

这里有用的信息比较多:

outcome 执行结果:passed or failed

nodeid case名称

longrepr 执行失败原因

item.function.__doc__ case描述信息

举🌰:

conftest.py

  1.  
     
  2.  
    import pytest
  3.  
     
  4.  
     
  5.  
     
  6.  
    @pytest.hookimpl(hookwrapper=True, tryfirst=True)
  7.  
    def pytest_runtest_makereport(item, call):
  8.  
    print('-------------------------------')
  9.  
    # 获取常规的钩子方法的调用结果,返回一个result对象
  10.  
    out = yield
  11.  
     
  12.  
    print('用例的执行结果', out)
  13.  
     
  14.  
    # 获取调用结果的测试报告,返回一个report对象,report对象的属性
  15.  
    # 包括when(setup, call, teardown三个值)、nodeid(测试用例的名字)、
  16.  
    # outcome(用例执行的结果 passed, failed)
  17.  
    report = out.get_result()
  18.  
     
  19.  
    print('测试报告: %s' % report)
  20.  
    print('步骤:%s' % report.when)
  21.  
    print('nodeid: %s' % report.nodeid)
  22.  
     
  23.  
    # 打印函数注释信息
  24.  
    print('description: %s' % str(item.function.__doc__))
  25.  
    print('运行结果: %s' % report.outcome)
学新通

test_cs1.py

  1.  
     
  2.  
     
  3.  
    import pytest
  4.  
    def setup_function():
  5.  
    print(u"setup_function:每个用例开始前都会执行")
  6.  
     
  7.  
    def teardown_function():
  8.  
    print(u"teardown_function:每个用例结束后都会执行")
  9.  
     
  10.  
     
  11.  
    def test_01():
  12.  
    """ 用例描述:用例1"""
  13.  
    print("用例1-------")
  14.  
     
  15.  
     
  16.  
    def test_02():
  17.  
    """ 用例描述:用例2"""
  18.  
    print("用例2------")
  19.  
     
  20.  
     
  21.  
     
  22.  
    if __name__ == '__main__':
  23.  
    pytest.main(['-s'])
  24.  
     
学新通

运行结果:

  1.  
     
  2.  
    ============================= test session starts ==============================
  3.  
    collecting ... collected 2 items
  4.  
     
  5.  
    test_cs1.py::test_01 -------------------------------
  6.  
    用例的执行结果 <pluggy.callers._Result object at 0x7ffb48478090>
  7.  
    测试报告: <TestReport 'test_cs1.py::test_01' when='setup' outcome='passed'>
  8.  
    步骤:setup
  9.  
    nodeid: test_cs1.py::test_01
  10.  
    description: 用例描述:用例1
  11.  
    运行结果: passed
  12.  
    setup_function:每个用例开始前都会执行
  13.  
    -------------------------------
  14.  
    用例的执行结果 <pluggy.callers._Result object at 0x7ffb68786b90>
  15.  
    测试报告: <TestReport 'test_cs1.py::test_01' when='call' outcome='passed'>
  16.  
    步骤:call
  17.  
    nodeid: test_cs1.py::test_01
  18.  
    description: 用例描述:用例1
  19.  
    运行结果: passed
  20.  
    PASSED [ 50%]用例1-------
  21.  
    -------------------------------
  22.  
    用例的执行结果 <pluggy.callers._Result object at 0x7ffb48478090>
  23.  
    测试报告: <TestReport 'test_cs1.py::test_01' when='teardown' outcome='passed'>
  24.  
    步骤:teardown
  25.  
    nodeid: test_cs1.py::test_01
  26.  
    description: 用例描述:用例1
  27.  
    运行结果: passed
  28.  
    teardown_function:每个用例结束后都会执行
  29.  
     
  30.  
    test_cs1.py::test_02 -------------------------------
  31.  
    用例的执行结果 <pluggy.callers._Result object at 0x7ffb584ef710>
  32.  
    测试报告: <TestReport 'test_cs1.py::test_02' when='setup' outcome='passed'>
  33.  
    步骤:setup
  34.  
    nodeid: test_cs1.py::test_02
  35.  
    description: 用例描述:用例2
  36.  
    运行结果: passed
  37.  
    setup_function:每个用例开始前都会执行
  38.  
    -------------------------------
  39.  
    用例的执行结果 <pluggy.callers._Result object at 0x7ffb584ef710>
  40.  
    测试报告: <TestReport 'test_cs1.py::test_02' when='call' outcome='passed'>
  41.  
    步骤:call
  42.  
    nodeid: test_cs1.py::test_02
  43.  
    description: 用例描述:用例2
  44.  
    运行结果: passed
  45.  
    PASSED [100%]用例2------
  46.  
    -------------------------------
  47.  
    用例的执行结果 <pluggy.callers._Result object at 0x7ffb6876e390>
  48.  
    测试报告: <TestReport 'test_cs1.py::test_02' when='teardown' outcome='passed'>
  49.  
    步骤:teardown
  50.  
    nodeid: test_cs1.py::test_02
  51.  
    description: 用例描述:用例2
  52.  
    运行结果: passed
  53.  
    teardown_function:每个用例结束后都会执行
  54.  
     
  55.  
     
  56.  
    ============================== 2 passed in 0.42s ===============================
  57.  
     
  58.  
    Process finished with exit code 0
学新通

我们在conftest.py中实现pytest_runtest_makereport这个方法,系统在调用这个函数时,会自动注入传参。

2.2.2 pytest_collectstart

pytest框架,提供了pytest_collectstart 开始收集,收集每个模块的case信息

源码:

  1.  
     
  2.  
    def pytest_collectstart(collector: "Collector") -> None:
  3.  
    """Collector starts collecting."""

我们看一下传参collector: "Collector"源码

  1.  
     
  2.  
    class Collector(Node):
  3.  
    """Collector instances create children through collect() and thus
  4.  
    iteratively build a tree."""
  5.  
     
  6.  
    class CollectError(Exception):
  7.  
    """An error during collection, contains a custom message."""
  8.  
     
  9.  
    def collect(self) -> Iterable[Union["Item", "Collector"]]:
  10.  
    """Return a list of children (items and collectors) for this
  11.  
    collection node."""
  12.  
    raise NotImplementedError("abstract")
  13.  
     
  14.  
    # TODO: This omits the style= parameter which breaks Liskov Substitution.
  15.  
    def repr_failure( # type: ignore[override]
  16.  
    self, excinfo: ExceptionInfo[BaseException]
  17.  
    ) -> Union[str, TerminalRepr]:
  18.  
    """Return a representation of a collection failure.
  19.  
     
  20.  
    :param excinfo: Exception information for the failure.
  21.  
    """
  22.  
    if isinstance(excinfo.value, self.CollectError) and not self.config.getoption(
  23.  
    "fulltrace", False
  24.  
    ):
  25.  
    exc = excinfo.value
  26.  
    return str(exc.args[0])
  27.  
     
  28.  
    # Respect explicit tbstyle option, but default to "short"
  29.  
    # (_repr_failure_py uses "long" with "fulltrace" option always).
  30.  
    tbstyle = self.config.getoption("tbstyle", "auto")
  31.  
    if tbstyle == "auto":
  32.  
    tbstyle = "short"
  33.  
     
  34.  
    return self._repr_failure_py(excinfo, style=tbstyle)
  35.  
     
  36.  
    def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
  37.  
    if hasattr(self, "fspath"):
  38.  
    traceback = excinfo.traceback
  39.  
    ntraceback = traceback.cut(path=self.fspath)
  40.  
    if ntraceback == traceback:
  41.  
    ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
  42.  
    excinfo.traceback = ntraceback.filter()
  43.  
     
  44.  
     
  45.  
    def _check_initialpaths_for_relpath(session, fspath):
  46.  
    for initial_path in session._initialpaths:
  47.  
    if fspath.common(initial_path) == initial_path:
  48.  
    return fspath.relto(initial_path)
学新通

类Collector继承了Node类。我们查看一下Node源码。

  1.  
     
  2.  
    class Node(metaclass=NodeMeta):
  3.  
    """Base class for Collector and Item, the components of the test
  4.  
    collection tree.
  5.  
     
  6.  
    Collector subclasses have children; Items are leaf nodes.
  7.  
    """
  8.  
     
  9.  
    # Use __slots__ to make attribute access faster.
  10.  
    # Note that __dict__ is still available.
  11.  
    __slots__ = (
  12.  
    "name",
  13.  
    "parent",
  14.  
    "config",
  15.  
    "session",
  16.  
    "fspath",
  17.  
    "_nodeid",
  18.  
    "_store",
  19.  
    "__dict__",
  20.  
    )
  21.  
     
  22.  
    def __init__(
  23.  
    self,
  24.  
    name: str,
  25.  
    parent: "Optional[Node]" = None,
  26.  
    config: Optional[Config] = None,
  27.  
    session: "Optional[Session]" = None,
  28.  
    fspath: Optional[py.path.local] = None,
  29.  
    nodeid: Optional[str] = None,
  30.  
    ) -> None:
  31.  
    #: A unique name within the scope of the parent node.
  32.  
    self.name = name
  33.  
     
  34.  
    #: The parent collector node.
  35.  
    self.parent = parent
学新通

属性包括

  1.  
    "name",
  2.  
    "parent",
  3.  
    "config",
  4.  
    "session",
  5.  
    "fspath",
  6.  
    "_nodeid",
  7.  
    "_store",
  8.  
    "__dict__",

我们在 conftest.py文件下,打印一下这个几个属性。

  1.  
    def pytest_collectstart(collector:Collector):
  2.  
    print("开始用例收集")
  3.  
    print("集合名称:%s"%collector.name)
  4.  
    print("parent",collector.parent)
  5.  
    print("config",collector.config)
  6.  
    print("session",collector.session)
  7.  
    print("fspath",collector.fspath)
  8.  
    print("_nodeid",collector._nodeid)
  9.  
    print("_store", collector._store)
  10.  
    print("_dict__", collector.__dict__)
  11.  
    print("...........................")

输出结果:

  1.  
    collecting ... 开始用例收集
  2.  
    集合名称:ChenShuaiTest
  3.  
    parent None
  4.  
    config <_pytest.config.Config object at 0x7fc7a8244e50>
  5.  
    session <Session ChenShuaiTest exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=0>
  6.  
    fspath /Users/zhaohui/PycharmProjects/ChenShuaiTest
  7.  
    _nodeid
  8.  
    _store <_pytest.store.Store object at 0x7fc7b8993ad0>
  9.  
    _dict__ {'keywords': <NodeKeywords for node <Session ChenShuaiTest exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=0>>, 'own_markers': [], 'extra_keyword_matches': set(), 'testsfailed': 0, 'testscollected': 0, 'shouldstop': False, 'shouldfail': False, 'trace': <pluggy._tracing.TagTracerSub object at 0x7fc7c83bc210>, 'startdir': local('/Users/zhaohui/PycharmProjects/ChenShuaiTest/test_case/mytest'), '_initialpaths': frozenset({local('/Users/zhaohui/PycharmProjects/ChenShuaiTest/test_case/mytest/test_cs1.py')}), '_bestrelpathcache': _bestrelpath_cache(path=PosixPath('/Users/zhaohui/PycharmProjects/ChenShuaiTest')), 'exitstatus': <ExitCode.OK: 0>, '_fixturemanager': <_pytest.fixtures.FixtureManager object at 0x7fc7b89936d0>, '_setupstate': <_pytest.runner.SetupState object at 0x7fc7c8502bd0>, '_notfound': [], '_initial_parts': [(local('/Users/zhaohui/PycharmProjects/ChenShuaiTest/test_case/mytest/test_cs1.py'), [])], 'items': []}
  10.  
    ...........................
  11.  
    收集的用例个数---------------------:1
  12.  
    [<Module test_cs1.py>]
  13.  
    开始用例收集
  14.  
    集合名称:test_cs1.py
  15.  
    parent <Package mytest>
  16.  
    config <_pytest.config.Config object at 0x7fc7a8244e50>
  17.  
    session <Session ChenShuaiTest exitstatus=<ExitCode.OK: 0> testsfailed=0 testscollected=0>
  18.  
    fspath /Users/zhaohui/PycharmProjects/ChenShuaiTest/test_case/mytest/test_cs1.py
  19.  
    _nodeid test_case/mytest/test_cs1.py
  20.  
    _store <_pytest.store.Store object at 0x7fc7a83088d0>
  21.  
    _dict__ {'keywords': <NodeKeywords for node <Module test_cs1.py>>, 'own_markers': [], 'extra_keyword_matches': set()}
  22.  
    ...........................
  23.  
    收集的用例个数---------------------:8
  24.  
    [<Function test_01>, <Function test_02>, <Function test_03>, <Function test_04>, <Function test_05>, <Function test_06>, <Function test_07>, <Function test_08>]
  25.  
    collected 8 items
学新通

这里主要展示的就是收集时每个模块的case信息。感觉用处不是很大。

2.2.3 pytest_collectreport

pytest框架,提供了pytest_collectreport回调函数,收集完成后case的报告

源码:

  1.  
     
  2.  
    def pytest_collectreport(report: "CollectReport") -> None:
  3.  
    """Collector finished collecting."""

传参CollectReport 

  1.  
     
  2.  
    @final
  3.  
    class CollectReport(BaseReport):
  4.  
    """Collection report object."""
  5.  
     
  6.  
    when = "collect"
  7.  
     
  8.  
    def __init__(
  9.  
    self,
  10.  
    nodeid: str,
  11.  
    outcome: "Literal['passed', 'skipped', 'failed']",
  12.  
    longrepr,
  13.  
    result: Optional[List[Union[Item, Collector]]],
  14.  
    sections: Iterable[Tuple[str, str]] = (),
  15.  
    **extra,
  16.  
    ) -> None:
  17.  
    #: Normalized collection nodeid.
  18.  
    self.nodeid = nodeid
  19.  
     
  20.  
    #: Test outcome, always one of "passed", "failed", "skipped".
  21.  
    self.outcome = outcome
  22.  
     
  23.  
    #: None or a failure representation.
  24.  
    self.longrepr = longrepr
  25.  
     
  26.  
    #: The collected items and collection nodes.
  27.  
    self.result = result or []
  28.  
     
  29.  
    #: List of pairs ``(str, str)`` of extra information which needs to
  30.  
    #: marshallable.
  31.  
    # Used by pytest to add captured text : from ``stdout`` and ``stderr``,
  32.  
    # but may be used by other plugins : to add arbitrary information to
  33.  
    # reports.
  34.  
    self.sections = list(sections)
  35.  
     
  36.  
    self.__dict__.update(extra)
  37.  
     
  38.  
    @property
  39.  
    def location(self):
  40.  
    return (self.fspath, None, self.fspath)
  41.  
     
  42.  
    def __repr__(self) -> str:
  43.  
    return "<CollectReport {!r} lenresult={} outcome={!r}>".format(
  44.  
    self.nodeid, len(self.result), self.outcome
  45.  
    )
  46.  
     
学新通

类CollectReport继承了

属性:

nodeid: str,
outcome: "Literal['passed', 'skipped', 'failed']",
longrepr,
result: Optional[List[Union[Item, Collector]]],
sections: Iterable[Tuple[str, str]] = (),

我们把这些属性打印一下:

  1.  
    def pytest_collectreport(report: CollectReport):
  2.  
    print("收集的用例个数---------------------:%s"%len(report.result))
  3.  
    print("result",report.result)
  4.  
    print("nodeid", report.nodeid)
  5.  
    print("outcome", report.outcome)
  6.  
    print("result", report.result)
  7.  
    print("sections", report.sections)
  1.  
    collecting ... 收集的用例个数---------------------:1
  2.  
    result [<Module test_cs1.py>]
  3.  
    nodeid
  4.  
    outcome passed
  5.  
    result [<Module test_cs1.py>]
  6.  
    sections []
  7.  
    收集的用例个数---------------------:8
  8.  
    result [<Function test_01>, <Function test_02>, <Function test_03>, <Function test_04>, <Function test_05>, <Function test_06>, <Function test_07>, <Function test_08>]
  9.  
    nodeid test_case/mytest/test_cs1.py
  10.  
    outcome passed
  11.  
    result [<Function test_01>, <Function test_02>, <Function test_03>, <Function test_04>, <Function test_05>, <Function test_06>, <Function test_07>, <Function test_08>]
  12.  
    sections []
  13.  
    collected 8 items

感觉也没啥太有用的东西.... 

三、项目实战:

3.1 我们先实现报警工具

alarm_utils.py 下,AlarmUtils工具类

  1.  
    # -*- coding:utf-8 -*-
  2.  
    # @Author: 喵酱
  3.  
    # @time: 2022 - 09 -15
  4.  
    # @File: alarm_utils.py
  5.  
    import requests
  6.  
    import json
  7.  
     
  8.  
     
  9.  
    class AlarmUtils:
  10.  
    @staticmethod
  11.  
    def default_alram(alarm_content:str,phone_list:list):
  12.  
    requests.post('https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的机器人的key',
  13.  
    headers={'Content-Type': 'application/json'},
  14.  
    data=json.dumps(
  15.  
    {'msgtype': 'text', 'text': {'mentioned_mobile_list':phone_list,
  16.  
    'content': '{}'.format(alarm_content).encode('utf-8').decode(
  17.  
    "latin1")}},
  18.  
    ensure_ascii=False))
学新通
这里艾特人的方式,通过手机号,是一个list。这个方法需要传报警内容及艾特的手机号。

3.2 跨模块的全局变量

  1.  
    # -*- coding:utf-8 -*-
  2.  
    # @Author: 喵酱
  3.  
    # @time: 2022 - 09 -14
  4.  
    # @File: gol.py
  5.  
    # -*- coding: utf-8 -*-
  6.  
     
  7.  
    def _init(): # 初始化
  8.  
    global _global_dict
  9.  
    _global_dict = {}
  10.  
     
  11.  
     
  12.  
    def set_value(key, value):
  13.  
    # 定义一个全局变量
  14.  
    _global_dict[key] = value
  15.  
     
  16.  
     
  17.  
    def get_value(key):
  18.  
    # 获得一个全局变量,不存在则提示读取对应变量失败
  19.  
    try:
  20.  
    return _global_dict[key]
  21.  
    except:
  22.  
    print('读取' key '失败\r\n')
学新通

3.3 通过HOOK函数收集报警信息&报警

conftest.py

  1.  
     
  2.  
     
  3.  
    @pytest.hookimpl(hookwrapper=True, tryfirst=True)
  4.  
    def pytest_runtest_makereport(item, call)-> Optional[TestReport]:
  5.  
    print('-------------------------------')
  6.  
    # 获取常规的钩子方法的调用结果,返回一个result对象
  7.  
    out = yield
  8.  
    # '用例的执行结果', out
  9.  
     
  10.  
     
  11.  
     
  12.  
    # 获取调用结果的测试报告,返回一个report对象,report对象的属性
  13.  
    # 包括when(setup, call, teardown三个值)、nodeid(测试用例的名字)、
  14.  
    # outcome(用例执行的结果 passed, failed)
  15.  
    report = out.get_result()
  16.  
     
  17.  
    # 只关注用例本身结果
  18.  
    if report.when == "call":
  19.  
     
  20.  
    num = gol.get_value("total")
  21.  
    gol.set_value("total",num 1)
  22.  
    if report.outcome != "passed":
  23.  
    failnum = gol.get_value("fail_num")
  24.  
    gol.set_value("fail_num", failnum 1)
  25.  
    single_fail_content = "{}.报错case名称:{},{},失败原因:{}".format(gol.get_value("fail_num"), report.nodeid,
  26.  
    str(item.function.__doc__),report.longrepr)
  27.  
    list_content:list = gol.get_value("fail_content")
  28.  
    list_content.append(single_fail_content)
  29.  
     
  30.  
     
  31.  
     
  32.  
     
  33.  
     
  34.  
     
  35.  
    @pytest.fixture(scope="session", autouse=True)
  36.  
    def fix_a():
  37.  
    gol._init()
  38.  
    gol.set_value("total",0)
  39.  
    gol.set_value("fail_num", 0)
  40.  
    # 失败内容
  41.  
    gol.set_value("fail_content", [])
  42.  
     
  43.  
    yield
  44.  
     
  45.  
    # 执行case总数
  46.  
    all_num = str(gol.get_value("total"))
  47.  
    # 失败case总数:
  48.  
    fail_num = str(gol.get_value("fail_num"))
  49.  
    if int(gol.get_value("fail_num")):
  50.  
    list_content: list = gol.get_value("fail_content")
  51.  
    final_alarm_content = "项目名称:{},\n执行case总数:{},失败case总数:{},\n详情:{}".format("陈帅的测试项目", all_num, fail_num,
  52.  
    str(list_content))
  53.  
    print(final_alarm_content )
  54.  
    AlarmUtils.default_alram(final_alarm_content, ['1871xxxxxx'])
  55.  
     
  56.  
     
学新通

大概是这个样子了,后续一些细节慢慢优化吧

pytest进行二次开发,主要依赖于Hook函数。在conftest.py文件里调用Hook函数。

conftest.py是pytest特有的本地测试配置文件,既可以用来设置项目级的fixture,也可以用来导入外部插件,还可以指定钩子函数。

conftest.py文件名称是固定的,pytest会自动设别该文件,只作用在它的目录以及子目录。

通过装饰器@pytest.fixture来告诉pytest某个特定的函数是一个fixture,然后用例可以直接把fixture当参数来调用

这里的前置后置@pytest.fixture(scope="session", autouse=True),是项目级的。只运行一次。

下一章:


 

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhggbcch
系列文章
更多 icon
同类精品
更多 icon
继续加载