目录:
- 安装及入门
- 使用和调用方法
- 原有TestSuite使用方法
- 断言的编写和报告
- Pytest fixtures:清晰 模块化 易扩展
- 使用Marks标记测试用例
- Monkeypatching/对模块和环境进行Mock
- 使用tmp目录和文件
- 捕获stdout及stderr输出
- 捕获警告信息
- 模块及测试文件中集成doctest测试
- skip及xfail: 处理不能成功的测试用例
- Fixture方法及测试用例的参数化
- 缓存: 使用跨执行状态
- unittest.TestCase支持
- 运行Nose用例
- 经典xUnit风格的setup/teardown
- 安装和使用插件
- 插件编写
- 编写钩子(hook)方法
- 运行日志
- API参考
- 优质集成实践
- 片状测试
- Pytest导入机制及sys.path/PYTHONPATH
- 配置选项
- 示例及自定义技巧
- Bash自动补全设置
Fixture方法及测试用例的参数化
pytest在多个级别启用测试参数化:
@pytest.mark.parametrize允许在测试函数或类中定义多组参数和Fixture。
pytest_generate_tests允许用户定义自定义参数化方案或扩展。
@pytest.mark.parametrize
:参数化测试函数
2.2版中的新功能。
版本2.4中的更改:一些改进。
内置的pytest.mark.parametrize装饰器支持测试函数的参数的参数化。以下是测试函数的典型示例,该函数实现检查某个输入是否导致预期输出:
# content of test_expectation.py
import pytest
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
assert eval(test_input) == expected
这里,@parametrize
装饰器定义了三个不同的(test_input,expected)
元组,以便test_eval
函数依次使用它们运行三次:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 3 items
test_expectation.py ..F [100%]
================================= FAILURES =================================
____________________________ test_eval[6*9-42] _____________________________
test_input = '6*9', expected = 42
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
> assert eval(test_input) == expected
E AssertionError: assert 54 == 42
E + where 54 = eval('6*9')
test_expectation.py:6: AssertionError
==================== 1 failed, 2 passed in 0.12 seconds ====================
注意
默认情况下,pytest会转义unicode字符串中用于参数化的任何非ascii字符,因为它有几个缺点。但是,如果你想在参数化中使用unicode字符串并在终端中按原样(非转义)查看它们,请在以下位置使用此选项pytest.ini
:
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
但请记住,这可能会导致不必要的副作用甚至是错误,具体取决于所使用的操作系统和当前安装的插件,因此使用它需要你自担风险。
如本例所示,只有一对输入/输出值无法通过简单的测试方法。和通常的测试函数参数一样,你可以在traceback中看到input
和output
值。
请注意,你还可以在类或模块上使用参数化标记(请参阅使用属性标记测试函数),这将使用参数集调用多个函数。
也可以在参数化中标记单个测试实例,例如使用内置mark.xfail
:
# content of test_expectation.py
import pytest
@pytest.mark.parametrize(
"test_input,expected",
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
我们运行这个:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 3 items
test_expectation.py ..x [100%]
=================== 2 passed, 1 xfailed in 0.12 seconds ====================
之前导致失败的一个参数集现在显示为“xfailed(预期失败)”测试。
如果提供的值parametrize
导致空列表 - 例如,如果它们是由某个函数动态生成的 - 则pytest的行为由该empty_parameter_set_mark
选项定义。
要获得多个参数化参数的所有组合,你可以堆叠 parametrize
装饰器:
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
pass
这将运行与设定参数的测试x=0/y=2
,x=1/y=2
, x=0/y=3
,并x=1/y=3
在装饰的秩序排气参数。
基本的pytest_generate_tests
例子
有时你可能希望实现自己的参数化方案或实现一些动力来确定Fixture的参数或范围。为此,你可以使用pytest_generate_tests
在收集测试函数时调用的钩子。通过传入的 metafunc
对象,你可以检查请求的测试上下文,最重要的是,你可以调用metafunc.parametrize()
以引起参数化。
例如,假设我们想要运行一个测试,我们想通过一个新的pytest
命令行选项设置字符串输入。让我们首先编写一个接受stringinput
fixture函数参数的简单测试:
# content of test_strings.py
def test_valid_string(stringinput):
assert stringinput.isalpha()
现在我们添加一个conftest.py
包含命令行选项和测试函数参数化的文件:
# content of conftest.py
def pytest_addoption(parser):
parser.addoption(
"--stringinput",
action="append",
default=[],
help="list of stringinputs to pass to test functions",
)
def pytest_generate_tests(metafunc):
if "stringinput" in metafunc.fixturenames:
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
如果我们现在传递两个stringinput值,我们的测试将运行两次:
$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
.. [100%]
2 passed in 0.12 seconds
让我们运行一个stringinput导致测试失败:
$ pytest -q --stringinput="!" test_strings.py
F [100%]
================================= FAILURES =================================
___________________________ test_valid_string[!] ___________________________
stringinput = '!'
def test_valid_string(stringinput):
> assert stringinput.isalpha()
E AssertionError: assert False
E + where False = <built-in method isalpha of str object at 0xdeadbeef>()
E + where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
test_strings.py:4: AssertionError
1 failed in 0.12 seconds
正如所料,我们的测试方法失败
如果你没有指定stringinput,它将被跳过,因为 metafunc.parametrize()
将使用空参数列表调用:
$ pytest -q -rs test_strings.py
s [100%]
========================= short test summary info ==========================
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:2
1 skipped in 0.12 seconds
请注意,metafunc.parametrize
使用不同的参数集多次调用时,这些集合中的所有参数名称都不能重复,否则将引发错误。
更多例子
有关更多示例,你可能需要查看更多参数化示例。