如何测试 pytest 夹具是否引发异常? [英] How can I test if a pytest fixture raises an exception?
问题描述
用例:在 pytest
测试套件中,我有一个 @fixture
如果缺少其配置的命令行选项,它会引发异常.我已经使用 xfail
为这个装置编写了一个测试:
Use case: In a pytest
test suite I have a @fixture
which raises exceptions if command line options for its configuration are missing. I've written a test for this fixture using xfail
:
import pytest
from <module> import <exception>
@pytest.mark.xfail(raises=<exception>)
def test_fixture_with_missing_options_raises_exception(rc_visard):
pass
但是,运行测试后的输出并未将测试声明为通过,而是xfailed":
However the output after running the tests does not state the test as passed but "xfailed" instead:
============================== 1 xfailed in 0.15 seconds ========================
除此之外,我无法测试 fixture
是否引发特定缺失命令行选项的异常.
In addition to that I am not able to test if the fixture
raises the exception for specific missing command line options.
有没有更好的方法来做到这一点?我可以以某种方式模拟 pytest
命令行选项,而不需要通过 pytest --<commandline-option-a> 调用特定测试吗?<test-file-name>::<test-name>
.
Is there a better approach to do this? Can I mock the pytest
command line options somehow that I do not need to call specific tests via pytest --<commandline-option-a> <test-file-name>::<test-name>
.
推荐答案
初始设置
假设您有一个包含以下代码的 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
如果没有指定.如果除了通过 --foo
传递的 bar
之外还有其他任何东西,fixture 会引发 ValueError
.
It adds a new command line arg --foo
and a fixture foo
returning the passed arg, or bar
if not specified. If anything else besides bar
passed via --foo
, the fixture raises a ValueError
.
你像往常一样使用夹具,例如
You use the fixture as usual, for example
def test_something(foo):
assert foo == 'bar'
现在让我们测试该夹具.
Now let's test that fixture.
在这个例子中,我们需要先做一些简单的重构.将夹具和相关代码移动到名为 conftest.py
之外的其他文件中,例如 my_plugin.py
:
In this example, we need to do some simple refactoring first. Move the fixture and related code to some file called something else than conftest.py
, for example, 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
中,确保加载了新插件:
In conftest.py
, ensure the new plugin is loaded:
# conftest.py
pytest_plugins = ['my_plugin']
运行现有的测试套件以确保我们没有破坏任何东西,所有测试仍应通过.
Run the existing test suite to ensure we didn't break anything, all tests should still pass.
pytest
提供了一个额外的插件来编写插件测试,称为 pytester
.默认情况下它不会激活,因此您应该手动执行此操作.在 conftest.py
中,使用 pytester
扩展插件列表:
pytest
provides an extra plugin for writing plugin tests, called pytester
. It is not activated by default, so you should do that manually. In conftest.py
, extend the plugins list with pytester
:
# conftest.py
pytest_plugins = ['my_plugin', 'pytester']
编写测试
一旦 pytester
处于活动状态,您将获得一个名为 testdir
的新装置.它可以从代码生成和运行 pytest
测试套件.这是我们的第一个测试的样子:
writing the tests
Once pytester
is active, you get a new fixture available called testdir
. It can generate and run pytest
test suites from code. Here's what our first test will look like:
# 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()
开始测试运行,产生一个我们可以检查的结果.
It should be pretty obvious what happens here: we provide the tests code as string and testdir
will generate a pytest
project from it in some temporary directory. To ensure our foo
fixture is available in the generated test project, we pass it in the generated conftest
same way as we do in the real one. testdir.runpytest()
starts the test run, producing a result that we can inspect.
让我们添加另一个测试来检查 foo
是否会引发 ValueError
:
Let's add another test that checks whether foo
will raise a 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
执行生成的测试,然后验证一个测试是否以错误结束并且错误输出包含预期的错误消息.
Here we execute the generated tests with --foo baz
and verify afterwards if one test ended with an error and the error output contains the expected error message.
这篇关于如何测试 pytest 夹具是否引发异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!