【问题标题】:How can I test if a pytest fixture raises an exception?如何测试 pytest 夹具是否引发异常?
【发布时间】:2018-05-14 14:02:42
【问题描述】:

用例:在pytest 测试套件中,我有一个@fixture,如果缺少其配置的命令行选项,则会引发异常。我已经使用xfail 为这个夹具编写了一个测试:

import pytest
from <module> import <exception>

@pytest.mark.xfail(raises=<exception>)
def test_fixture_with_missing_options_raises_exception(rc_visard):
    pass

但是,运行测试后的输出并未表明测试已通过,而是“xfailed”:

============================== 1 xfailed in 0.15 seconds ========================

除此之外,我无法测试 fixture 是否引发特定缺失命令行选项的异常。

有没有更好的方法来做到这一点?我可以以某种方式模拟pytest 命令行选项,而无需通过pytest --&lt;commandline-option-a&gt; &lt;test-file-name&gt;::&lt;test-name&gt; 调用特定测试。

【问题讨论】:

  • 为什么夹具中会引发异常?你想做什么?如果您正在测试一个函数是否引发异常,您应该在测试本身中调用它,在 with pytest.raises(MyError) 块内。
  • 这不是重复的,因为在那个问题中没有夹具引发异常。
  • 我想确保对于夹具没有“缺少其配置的命令行选项”。
  • 哦,所以你想为夹具本身写一个测试?
  • 是的。原来,例如复杂的命令行选项容易出错 w.r.t.可用性。

标签: python testing pytest


【解决方案1】:

初始设置

假设您有一个带有conftest.py 的简化项目,其中包含以下代码:

import pytest


def pytest_addoption(parser):
    parser.addoption('--foo', action='store', dest='foo', default='bar',
                     help='--foo should be always bar!')

@pytest.fixture
def foo(request):
    fooval = request.config.getoption('foo')
    if fooval != 'bar':
        raise ValueError('expected foo to be "bar"; "{}" provided'.format(fooval))

它添加了一个新的命令行参数--foo 和一个夹具foo 返回传递的参数,或者bar 如果未指定。如果除了bar 之外的任何其他内容通过--foo 传递,则夹具会引发ValueError

你照常使用夹具,例如

def test_something(foo):
    assert foo == 'bar'

现在让我们测试一下这个夹具。

准备工作

在这个例子中,我们需要先做一些简单的重构。将夹具和相关代码移动到某个名为 conftest.py 以外的文件,例如 my_plugin.py

# my_plugin.py

import pytest


def pytest_addoption(parser):
    parser.addoption('--foo', action='store', dest='foo', default='bar',
                     help='--foo should be always bar!')

@pytest.fixture
def foo(request):
    fooval = request.config.getoption('foo')
    if fooval != 'bar':
        raise ValueError('expected foo to be "bar"; "{}" provided'.format(fooval))

conftest.py 中,确保加载了新插件:

# conftest.py

pytest_plugins = ['my_plugin']

运行现有的测试套件以确保我们没有破坏任何东西,所有测试仍应通过。

激活pytester

pytest 提供了一个额外的插件来编写插件测试,称为pytester。默认情况下未激活它,因此您应该手动执行此操作。在conftest.py 中,使用pytester 扩展插件列表:

# conftest.py

pytest_plugins = ['my_plugin', 'pytester']

编写测试

一旦pytester 处于活动状态,您将获得一个名为testdir 的新设备。它可以从代码生成和运行pytest 测试套件。这是我们的第一个测试的样子:

# test_foo_fixture.py

def test_all_ok(testdir):

    testdata = '''
               def test_sample(foo):
                   assert True
               '''

    testconftest = '''
                   pytest_plugins = ['my_plugin']
                   '''

    testdir.makeconftest(testconftest)
    testdir.makepyfile(testdata)
    result = testdir.runpytest()
    result.assert_outcomes(passed=1)

这里发生的事情应该很明显:我们将测试代码作为字符串提供,testdir 将在某个临时目录中从中生成一个pytest 项目。为了确保我们的foo 夹具在生成的测试项目中可用,我们在生成的conftest 中传递它,就像我们在真实项目中所做的那样。 testdir.runpytest() 开始测试运行,产生我们可以检查的结果。

让我们添加另一个测试来检查foo 是否会引发ValueError

def test_foo_valueerror_raised(testdir):
    testdata = '''
               def test_sample(foo):
                   assert True
               '''

    testconftest = '''
                   pytest_plugins = ['my_plugin']
                   '''

    testdir.makeconftest(testconftest)
    testdir.makepyfile(testdata)
    result = testdir.runpytest('--foo', 'baz')                                                                                                                                
    result.assert_outcomes(error=1)
    result.stdout.fnmatch_lines([
        '*ValueError: expected foo to be "bar"; "baz" provided'
    ])

这里我们使用--foo baz 执行生成的测试,然后验证一个测试是否以错误结束并且错误输出包含预期的错误消息。

【讨论】:

  • 非常感谢。一旦我尝试过,我会接受答案。
猜你喜欢
  • 2018-01-16
  • 2010-09-12
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多