Hook 方法之 pytest_runtest_makereport:
@hookspec(firstresult=True)
def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object
for the given :py:class:`pytest.Item <_pytest.main.Item>` and
:py:class:`_pytest.runner.CallInfo`.
Stops at first non-None result, see :ref:`firstresult` """
官方的不太懂,根据自己使用后的理解(不一定精准),这个钩子方法的大概作用是:
对于给定的测试用例(item)和调用步骤(call),返回一个测试报告对象(_pytest.runner.TestReport);具体表现为:这个钩子方法会被每个测试用例调用 3 次,分别是:
- 用例的 setup 执行完毕后,调用 1 次,返回 setup 的执行结果;
- 用例执行完毕之后,调用 1 次,返回测试用例的执行结果;
- 用例的 teardown 执行完毕后,调用1 次,返回 teardown 的执行结果;
获取测试用例的执行结果:
准备测试用例:
class TestDemoA:
def test_A_001(self):
pass
调用钩子方法: 用不到 call 和 item 参数;
# conftest.py
import pytest
@pytest.fixture(autouse=True)
def fix_result():
# setup
print()
yield
# teardown
print()
# 1. 调用钩子方法, item 参数这里不用
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport():
print('------------------------------------')
# 2. 获取钩子方法的调用结果
result = yield
print('钩子方法的执行结果', result)
# 3. 从钩子方法的调用结果中获取测试报告
report = result.get_result()
print('从结果中获取测试报告:', report)
print('从报告中获取 nodeid:', report.nodeid)
print('从报告中获取调用步骤:', report.when)
print('从报告中获取执行结果:', report.outcome)
if __name__ == '__main__':
pytest.main(['-s', '-q'])
执行结果:
从结果中我们可以看到每次调用返回的 Result 对象和 TestReport对象,以及对象属性;
对象属性就是我们想要的信息;
当setup、测试用例、teardown 执行失败时的结果:
测试用例:修改为断言失败;
class TestDemoA:
def test_A_001(self):
# 断言失败
assert 0
调用钩子方法:精简一下代码;
修改 fixture:控制 setup 和 teardown 执行失败;
# conftest.py
import pytest
# P=100 时 setup 失败;
# P=1000时 teardown 失败;
P = 100
@pytest.fixture(autouse=True)
def fix_result():
global P
if P == 100:
raise Exception('fixture setup 执行出错!')
yield
if P == 100:
raise Exception('fixture teardown 执行出错!')
# 1. 调用钩子方法, item 参数这里不用
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport():
print()
print('------------------------------------')
# 2. 获取钩子方法的调用结果
result = yield
print('钩子方法的执行结果', result)
# 3. 从钩子方法的调用结果中获取测试报告
report = result.get_result()
print('当前用例的 nodeid:', report.nodeid)
print('当前用例执行步骤:', report.when)
print('执行结果:', report.outcome)
当 setup 失败时执行结果:
从结果上看:
setup 的执行结果是 failed,但是没有用例本身的执行结果,而且 teardown 的结果竟然是 passed; 这是因为pytest 有个规则,就是当 setup 执行失败时后面的测试用例和 teardown 就不会再执行,所以没有这次没有输出测试用例的执行结果,而且因为 teardown 没有执行,所以teardown 的结果没有失败(虽然不知道是为什么,感觉这是个bug!)
当 setup 成功,测试用例和teardown 失败时执行结果:
这次就正常了,步骤 call 就是测试用例本身的结果;
实际应用:获取用例执行结果处理用例依赖:
因为部分用例必须依赖前面某个用例,当前置用例失败后,后续用例必定会失败并且非常耗时,所以我们可以用这个钩子方法来处理这个问题,跳过依赖的用例;
# conftest.py
import pytest
# 定义一个类,来存储用例执行失败的结果
class Falied:
skip = False
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport():
result = yield
report = result.get_result()
# 当前用例名
case_name = report.nodeid.split('::')[-1]
# 当某个用例失败时(setup失败也算)或被跳过时,将这个用例的执行结果存储在 Failed 类中
if report.when in ('setup', 'call') and report.outcome in ('failed', 'skipped'):
setattr(Falied, case_name, True)
if __name__ == '__main__':
pytest.main(['-s', '-q'])
import pytest
from .conftest import Falied
class TestDemoA:
def test_A_001(self):
# 断言失败
assert 0
def test_A_002(self):
# 从 Failed 获取 test_A_001 的执行结果
if getattr(Falied, 'test_A_001', False):
pytest.skip('test_A_001 执行失败或被跳过,此用例跳过!')
def test_A_003(self):
pass
测试结果:因为 case 001 执行失败,所以 case 002 被跳过;
============================= test session starts =============================
collected 3 items
test_Z.py::TestDemoA::test_A_001 FAILED
test_Z.py::TestDemoA::test_A_002 SKIPPED
test_Z.py::TestDemoA::test_A_003 PASSED
=================== 1 failed, 1 passed, 1 skipped in 0.05s ====================