【问题标题】:pytest: How to suppress traceback for one test case onlypytest:如何仅抑制一个测试用例的回溯
【发布时间】:2021-02-01 19:17:00
【问题描述】:

我有以下测试用例:

    @pytest.mark.parametrize("init_resp", [True, False])
    @pytest.mark.parametrize("mechanism", ["login", "plain"])
    def test_byclient(self, auth_peeker_controller, client, mechanism, init_resp):
        self._ehlo(client)
        client.user = "goodlogin"
        client.password = "goodpasswd"
        auth_meth = getattr(client, "auth_" + mechanism)
        try:
            client.auth(mechanism, auth_meth, initial_response_ok=init_resp)
        except SMTPAuthenticationError:
            if (mechanism, init_resp) == ("login", False):
                client.docmd("*")
                pytest.xfail(reason="smtplib.SMTP.auth_login is buggy (bpo-27820)")
            else:
                raise
        peeker = auth_peeker_controller.handler
        assert isinstance(peeker, PeekerHandler)
        assert peeker.login == b"goodlogin"
        assert peeker.password == b"goodpasswd"

如您所见,对于一种参数组合(即("login", False)),测试预计会失败。

问题是,我仍然有 2 个异常弄乱了 pytest 的输出:

aiosmtpd\tests\test_smtp.py:1054: in test_byclient
    client.auth(mechanism, auth_meth, initial_response_ok=init_resp)
C:\Python\36\lib\smtplib.py:642: in auth
    raise SMTPAuthenticationError(code, resp)
E   smtplib.SMTPAuthenticationError: (334, b'UGFzc3dvcmQA')

During handling of the above exception, another exception occurred:
aiosmtpd\tests\test_smtp.py:1058: in test_byclient
    pytest.xfail(reason="smtplib.SMTP.auth_login is buggy (bpo-27820)")
E   _pytest.outcomes.XFailed: smtplib.SMTP.auth_login is buggy (bpo-27820)

(pytest 使用--tb=short 运行)

是否可以抑制这些异常仅针对这一测试用例


编辑 1

其实刚发完这个问题,我就把测试用例改成:

    @pytest.mark.parametrize("init_resp", [True, False])
    @pytest.mark.parametrize("mechanism", ["login", "plain"])
    def test_byclient(self, auth_peeker_controller, client, mechanism, init_resp):
        self._ehlo(client)
        client.user = "goodlogin"
        client.password = "goodpasswd"
        auth_meth = getattr(client, "auth_" + mechanism)
        if (mechanism, init_resp) == ("login", False):
            with pytest.raises(SMTPAuthenticationError):
                client.auth(mechanism, auth_meth, initial_response_ok=init_resp)
            client.docmd("*")
            pytest.xfail(reason="smtplib.SMTP.auth_login is buggy (bpo-27820)")
        client.auth(mechanism, auth_meth, initial_response_ok=init_resp)
        peeker = auth_peeker_controller.handler
        assert isinstance(peeker, PeekerHandler)
        assert peeker.login == b"goodlogin"
        assert peeker.password == b"goodpasswd"

结果稍微好一点:

aiosmtpd\tests\test_smtp.py:1035: in test_byclient
    pytest.xfail(reason="smtplib.SMTP.auth_login is buggy (bpo-27820)")
E   _pytest.outcomes.XFailed: smtplib.SMTP.auth_login is buggy (bpo-27820)

不过,如果可能的话,我仍然希望抑制那个单一的“异常”。


上面的代码sn-p来自these lines on GitHub

【问题讨论】:

  • 重构它,使其在完全不同的情况下参数化。那么你的测试中也不需要条件逻辑。
  • @jonrsharpe 这不是“完全不同的情况”。所有情况都相似:我正在检查smtplib.SMTPaiosmtpd 之间的交互是否符合标准。将来,将添加额外的 AUTH 机制(例如,DIGEST-MD5、CRAM-MD5 等)bpo-27820 有点我,所以我必须 XFAIL 那个。

标签: python pytest


【解决方案1】:

如果您不介意明确写出要测试的每个参数组合,则很容易将其中一个组合标记为预期会失败。我个人更喜欢这种风格,因为我只是觉得它更清晰、更灵活:

@pytest.mark.parametrize(
    'init_resp,mechanism', [
        pytest.param(True,  'login'),
        pytest.param(False, 'login', marks=pytest.xfail),
        pytest.param(True,  'plain'),
        pytest.param(False, 'plain'),
    ]
)
def test_byclient(self, auth_peeker_controller, client, mechanism, init_resp):
    ...

也就是说,在我手中,您给出的示例没有输出任何异常。它只是说 3 次测试通过,1 次失败。我怀疑您的问题与 pytest 的配置方式有关,但如果您可以发布一个最小的工作示例会很有帮助。

【讨论】:

  • 使用 2x2 矩阵,展开是可行的。不过,在未来,我可能会遇到类似的情况,但使用 7x4 矩阵。在这种情况下,展开矩阵以列出所有 28 种组合是非常不切实际的。
  • 另外,XFAIL 不是由于 pytest 配置,而是由于 XFAIL 消息提示,这是由于 bpo-27820,我已提交 PR 24118。如果 PR 被合并,我将“取消失败”特定组合。
  • 对于一个最小的工作示例......这有点难。这是一个复杂的软件,XFAIL 发生在复杂的 SMTP 交互之后。因此,我没有试图“最小化”一个“工作示例”,而是在已编辑的问题中提供了指向我的 GitHub 存储库的直接链接。
【解决方案2】:

您可以通过多种方式将xfail 标记附加到测试函数之外。例如,在测试收集完成时:

import pytest


def pytest_collection_modifyitems(items):
    for item in items:
        if item.name == "test_byclient[login-False]":
            item.add_marker(pytest.mark.xfail(reason="..."))

(将该代码放在测试根目录中的 conftest.py 中)。

或通过autouse 固定装置:

@pytest.fixture(autouse=True)
def _(request):
    if request.node.name == "test_byclient[login-False]":
        request.node.add_marker(pytest.mark.xfail(reason="..."))

这样,login-False-specific 代码可以从测试函数中全部删除:

@pytest.mark.parametrize("init_resp", [True, False])
@pytest.mark.parametrize("mechanism", ["login", "plain"])
def test_byclient(self, auth_peeker_controller, client, mechanism, init_resp):
    self._ehlo(client)
    client.user = "goodlogin"
    client.password = "goodpasswd"
    auth_meth = getattr(client, "auth_" + mechanism)
    client.auth(mechanism, auth_meth, initial_response_ok=init_resp)
    peeker = auth_peeker_controller.handler
    assert isinstance(peeker, PeekerHandler)
    assert peeker.login == b"goodlogin"
    assert peeker.password == b"goodpasswd"

如果client.docmd("*")login-False-test 的一个组成部分,那么我同意@jonrsharpe:你有一个覆盖两个不同测试用例的测试函数,你应该把它分成两个独立的测试用例。

【讨论】:

  • client.docmd("*") 部分并不是测试的真正部分,但没有它,smtplib.SMTP 将产生另一个异常,因为它以某种方式被“困”在 SMTP AUTH 阶段。让我们看看我是否可以将client.docmd("*") 移动到client 夹具中并仅在需要时调用它...
【解决方案3】:

您的测试用例不应与您的 PROD 代码相似,其中您有 try except 异常。 pytest 提供了一种机制来期待异常然后处理它。以下是代码。

with pytest.raises(SMTPAuthenticationError) as exception:
        client.auth(mechanism, auth_meth, initial_response_ok=init_resp)
        .... do something.

【讨论】:

  • 实际上在我发布我的问题后不久就实现了。我已经编辑了我的问题以反映当前的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-03
  • 1970-01-01
  • 2020-02-15
  • 2012-07-10
  • 1970-01-01
  • 1970-01-01
  • 2021-10-20
相关资源
最近更新 更多