自定义 pytest junitxml 失败报告

Customizing pytest junitxml failure reports

我正在尝试反省测试失败并将其他数据包含到 junit xml 测试报告中。具体来说,这是一套针对外部产品的功能测试,我想将产品的日志包含在故障报告中。

使用找到的方法 here,我能够在执行 multicall 之前将日志打印到 stdout,最终显示在 jenkin 的失败报告中。但我确信有更好的方法来实现这一目标。

我尝试使用 pytest_runtest_logreport 挂钩将日志附加到 'sections' 属性中,该属性已经包含 'captured stdout' 和 'captured stderr' 流。但是新添加的部分不会进入 xml 文件。我也在 pytest_runtest_makereport 挂钩中直接尝试了上述技术,结果相似。

pytest 2.7 的发行说明指出 2.8 不再使用多调用支持,@pytest.mark.hookwrapper 是新的方法,但我似乎根本无法做到这一点 - "yield" returns None 而不是 CallOutcome 对象(在 makereport 挂钩中尝试过)。即使它返回了一些东西,我也不确定我是否可以向它添加会显示在 xml 报告中的东西。

是否有任何我缺少的功能可以让我以灵活的方式执行此操作? (灵活的意思是:不被绑定到标准输出或像捕获日志插件那样记录调用)

要向测试报告(XML、控制台或其他)添加信息,请查看 reporting hooks, more specifically at pytest_runtest_logreport

编辑:由于我需要访问测试项目的 funcargs(和测试结果)以进行报告,因此我能够将逻辑移至 pytest_runtest_makereport(item, __multicall__) 挂钩。诀窍是执行多重调用,returns 报告 object:

@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call, __multicall__):
    report = __multicall__.execute()
    # then I was able to manipulate report and get the same results as below

Bruno 的回答给了我更彻底分析此功能所需的动力:)

它是这样工作的:

def pytest_runtest_logreport(report):
    if report.failed:
        report.longrepr.sections.append(("Header", "Message", "-"))
        report.sections.append(("Captured stdout", "This is added to stdout"))
        report.sections.append(("Captured stderr", "This is added to stderr"))
        report.sections.append(("Custom Section", "This can only be seen in the console - the xml won't have it."))

longrepr 属性仅在失败时可用。它需要一个 3 元组,最后一个值是用来修饰 decorate/surround 和 header 的字符。它将出现在报告的 "failure" 部分:

----------------------------------- Header ------------------------------------
Message

自定义部分将创建额外的结果部分以打印到控制台。但他们不会进入 junitxml:

------------------------------- Custom Section --------------------------------
This can only be seen in the console - the xml won't have it.

junitxml 报告只有 2 个部分:out 和 err。要向其中添加自定义文本,您必须创建名为 "Captured std" 的部分,并且只有这些部分才能进入 xml 文件。任何其他名称将生成一个只能在控制台中看到的自定义部分。

这是使用上面的代码生成的 junitxml,为此需要重新格式化 post:

<?xml version="1.0" encoding="utf-8" ?> 
<testsuite errors="0" failures="1" name="pytest" skips="0" tests="1" time="0.646">
  <testcase classname="test_reporting" name="test_fail" time="0.000999927520752">
    <failure message="test failure">
      @ut def test_fail(): > assert 0, "It failed"
      E AssertionError: It failed 
      E assert 0 test_reporting.py:346: AssertionError
      ----------------------------------- Header ------------------------------------
      Message
    </failure> 
    <system-out>This is added to stdout</system-out> 
    <system-err>This is added to stderr</system-err> 
  </testcase>
</testsuite>

pytest_runtest_makereport(item, call)@pytest.hookimpl(hookwrapper=True) 结合使用允许在使用报告创建 xml 之前访问报告,例如

@pytest.hookimpl(hookwrapper=True)                                                                                                                                                                                                                                                                                         
def pytest_runtest_makereport(item, call):
    result = yield                                                                                                                                                                                                                                                                                         
    if result.get_result().failed:
        report.longrepr.addsection("Header","message")

使用 pytest-3.9.3

虽然 the documentation 对于 hookwrapper 确实建议谨慎

If the result of the underlying hook is a mutable object, they may modify that result but it’s probably better to avoid it.