无法在装饰器中捕获 pytest 的结果

Posted

技术标签:

【中文标题】无法在装饰器中捕获 pytest 的结果【英文标题】:unable to capture results of pytest in decorator 【发布时间】:2019-01-20 13:21:32 【问题描述】:

我的 pytest 测试装饰器在调用函数后立即退出装饰器。如果我使用 python 而不是 pytest 运行文件,效果很好。

代码如下:

def dec(func):
    def wrapper(*args, **kwargs):
        print('do some stuff')
        result = func(*args, **kwargs)
        print('ran function')
        return False if result else return True
    return wrapper

@dec
def test_something_is_not_true():
    return False # assert False

@dec
def test_something_is_true():
    return True # assert True


print(test_something_is_not_true())
print(test_something_is_true())

我用python运行这个文件的结果如下:

C:\tests> python test.py
do some stuff
ran function
True
do some stuff
ran function
False

效果很好!

但是当我使用 pytest 运行它时,它会停在 result = func(*args, **kwargs) 行并且永远不会执行 print('ran function') 行:

C:\tests>pytest test.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: C:\, inifile:
plugins: metadata-1.7.0, jira-0.3.6, html-1.19.0
collected 2 items

test.py F.                                                               [100%]

================================== FAILURES ===================================
_________________________ test_something_is_not_true __________________________

args = (), kwargs = 

    def wrapper(*args, **kwargs):
        print('do some stuff')
>       result = func(*args, **kwargs)

test.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    @dec
    def test_something_is_not_true():
>       assert False
E       assert False

test.py:31: AssertionError
---------------------------- Captured stdout call -----------------------------
do some stuff
===================== 1 failed, 1 passed in 0.11 seconds ======================

如您所见,装饰器函数实际上并没有做任何事情......但如果我能让它工作,那么我可以查看测试是否在该函数中通过,如果是,则根据结果执行一些额外的逻辑测试的通过或失败。也许使用装饰器来完成捕获测试的输出不是最好的方法吗?

你会怎么做?

【问题讨论】:

使用装饰器绝对不是解决这个问题的好方法。改用固定装置。 return Falseassert False 是两个不同的东西。断言将引发AsserionError,中止程序。我怀疑使用解释器“正常”执行脚本会打印出你ran function 【参考方案1】:

如果您需要在测试前后执行代码,可以在fixture 中完成。示例:

import pytest

@pytest.fixture
def wrapper(request):
    print('\nhere I am before the test')
    print('test function name is', request.node.name)
    print('test file is located under', request.node.fspath)
    yield
    print('\nand here I am after the test')


@pytest.mark.usefixtures('wrapper')
def test_spam_good():
    assert True

@pytest.mark.usefixtures('wrapper')
def test_spam_bad():
    assert False

如您所见,这比编写自定义装饰器要强大得多。由于pytest 非常擅长围绕测试进行元编程,所以您可能需要的很多东西已经在那里,您只需要知道如何访问它。 pytest docs 包含许多适合初学者的示例和食谱。


如果您需要夹具中的测试结果,请参阅文档中的配方:Making test result information available in fixtures。在项目根目录下创建一个文件conftest.py,内容如下:

import pytest


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    # execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # set a report attribute for each phase of a call, which can
    # be "setup", "call", "teardown"

    setattr(item, "rep_" + rep.when, rep)

现在您可以通过自定义夹具中的request 夹具访问测试结果:

@pytest.fixture
def something(request):
    yield
    # request.node is an "item" because we use the default
    # "function" scope
    if request.node.rep_setup.failed:
        print("setting up a test failed!", request.node.nodeid)
    elif request.node.rep_setup.passed:
        if request.node.rep_call.failed:
            print("executing test failed", request.node.nodeid)

【讨论】:

以上是关于无法在装饰器中捕获 pytest 的结果的主要内容,如果未能解决你的问题,请参考以下文章

如何避免硬编码?在装饰器中

如何绕过 pytest 夹具装饰器?

@Patch 装饰器与 pytest 夹具不兼容

如何通过自定义装饰器提供非夹具 pytest 参数?

pytest运行方式及装饰器的使用

在装饰器中修改函数