【问题标题】:In pytest, how can I abort the fixture teardown?在 pytest 中,如何中止夹具拆卸?
【发布时间】:2020-04-16 20:23:20
【问题描述】:

我们的 pytest 环境有很多固定装置(主要是 scope='function'scope='module'),它们正在执行以下形式的操作:

@pytest.yield_fixture(scope='function')
def some_fixture():
    ... some object initialization ...
    yield some_object
    ... teardown ...

我们使用夹具的拆卸阶段(在 yield 之后)删除一些专门为测试创建的资源。

但是,如果测试失败,我不希望执行拆解,因此我们将拥有仍然存在的资源以进行进一步调试。

例如,以下是在我们所有测试框架中重复出现的常见场景:

@pytest.yield_fixture(scope='function')
def obj_fixture():
    obj = SomeObj.create()
    yield obj
    obj.delete()

def test_obj_some_field(obj_fixture):
    assert obj_fixture.some_field is True

在这种情况下,如果assert 中的条件是True,我希望obj.delete() 执行。 但是,如果测试失败,我希望 pytest 跳过 obj.delete() 以及 yield 之后的任何其他内容。

谢谢。

编辑 我希望在不更改夹具和测试代码的情况下完成该过程,我更喜欢自动过程而不是在我们的整个测试代码库中进行此重构。

【问题讨论】:

    标签: python pytest


    【解决方案1】:

    pytest docs 中有一个关于如何执行此操作的示例。基本思想是,您需要在钩子函数中捕获此信息并将其添加到测试item 中,该测试可在测试request 上使用,该测试可通过request 夹具提供给夹具/测试。

    对你来说,它看起来像这样:

    # 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)
    
    # test_obj.py
    
    import pytest
    
    
    @pytest.fixture()
    def obj(request):
        obj = 'obj'
        yield obj
    
        # setup succeeded, but the test itself ("call") failed
        if request.node.rep_setup.passed and request.node.rep_call.failed:
            print(' dont kill obj here')
        else:
            print(' kill obj here')
    
    
    def test_obj(obj):
        assert obj == 'obj'
        assert False # force the test to fail
    

    如果你使用pytest -s 运行它(不让 pytest 捕获来自固定装置的输出),你会看到类似的输出

    foobar.py::test_obj FAILED dont kill obj here
    

    这表明我们正在命中条件的右分支。

    【讨论】:

    • 我实际上实现了这个,但不幸的是,这只是我的解决方案的一半。我最终得到了最初的问题,因为我想完全自动化解决方案 - 意思是没有if...else 子句,而只是在断言失败的yield 之后中止代码。
    • 我认为没有办法做到这一点。如果您需要在许多地方执行此操作,您可以将行为包装在“如果测试成功则运行回调”函数中,并(在夹具内部)将该函数传递给执行清理的回调。
    • 我会考虑你提出的解决方案,我同意我最初的目标可能是死路一条,谢谢!
    【解决方案2】:

    Teardown 旨在独立于测试通过或失败执行。

    所以我建议要么编写您的拆解代码,使其足够健壮,无论测试通过还是失败都可以执行,或者将清理添加到测试的末尾,这样只有在没有前面的情况下才会调用它断言失败,如果之前没有发生异常

    【讨论】:

    • 我们的存储库包含数千个测试,看起来有点像原始帖子中的示例,我不想重构所有方法,我正在寻找一个自动解决方案(也许与pytest 钩子,但我不确定这是正确的方向)。
    【解决方案3】:

    设置一个类级别的标志来指示通过/失败,并在您的拆解中检查它。这未经测试,但应该给你的想法::

    @pytest.yield_fixture(scope='function')
    def obj_fixture():
        obj = SomeObj.create()
        yield obj
        if this.passed:
          obj.delete()
    
    def test_obj_some_field(obj_fixture):
        assert obj_fixture.some_field is True
        this.passed = True
    

    【讨论】:

    • 抱歉不够精确 - 我的目标是提供一个不需要重构所有测试和固定装置的自动解决方案。我编辑了帖子。还是谢谢。
    【解决方案4】:

    我使用Makefile 来执行pytest,因此我可以使用其他工具。我也需要只在成功时清理固定装置。我在Makefiletest 方法中添加了清理作为第二个命令。

    clean:
        find . | grep -E "(__pycache__|\.pyc|\.pyo)" | xargs rm -rf
        -rm database.db  # the minus here allows this to fail quietly
    
    database:
        python -m create_database
    
    lint:
        black .
        flake8 .
    
    test: clean lint database
        pytest -x -p no:warnings
        rm -rf tests/mock/fixture_dir
    

    【讨论】:

      猜你喜欢
      • 2021-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多