在pytest parametrize中标记输入

2 投票
3 回答
2061 浏览
提问于 2025-04-18 12:56

我有一个测试,想把它放在两个不同的测试套件里运行,但每个套件需要不同的参数输入。测试套件是通过pytest的标记来区分的。

有没有办法给参数化的条目打标记,让它们只在特定的测试套件中运行呢?

我想要做到的是:

@pytest.mark.suiteA # include the test in Suite A
@pytest.mark.suiteB # include the test in Suite B
@pytest.mark.parametrize("inputParameter", [
                              (10),                    # use this input for Suites A and B
                              pytest.mark.suiteB(12)]) # only use this input for Suite B
def test_printInputParameter(inputParameter):
    print inputParameter

像这样运行代码并没有产生我想要的结果——两个输入都被用于两个套件。

我看到pytest允许在参数化中使用xfail或skip(可以参考http://pytest.org/latest/skipping.html上的“Skip/xfail with parametrize”)。如果能写一个条件语句,只在运行B套件时为真,那也能满足我的需求。

提前谢谢你的帮助。

3 个回答

0

你使用了 @pytest.mark.suiteX 这个标记,把所有的参数化测试都标记上了,这样实际上你就给所有的测试都加上了这个标记。其实应该只在参数列表里面加标记:

import pytest

mark = pytest.mark

@mark.parametrize("inputParameter", [
    mark.suiteA(10),
    mark.suiteB(12),
])
def test_printInputParameter(inputParameter):
    print inputParameter

然后在命令行中,使用 -m(标记)来选择你想要过滤的测试:

bash-4.4$ pytest -m suiteA test_in.py -sv
test_in.py::test_printInputParameter[10] 10
PASSED

bash-4.4$ pytest -m suiteB test_in.py -sv
test_in.py::test_printInputParameter[12] 12
PASSED
0

看起来你可以使用skipif标记来实现这个功能,具体的说明可以参考这个链接:http://pytest.org/latest/skipping.html#skip-xfail-with-parametrize(你提到的那个)。你只需要知道你的代码是运行在suiteA还是suiteB,这个你可能已经有办法判断,因为你已经在使用suiteASuiteB标记了。为了举个例子,我们可以在sys模块上设置一个(不太优雅的)辅助属性,就像你检测代码是否在py.test下运行一样:

# E.g. in conftest.py; in real life the value probably comes from a
# command line option added by pytest_addoption()
def pytest_configure(config):
    sys._my_test_suite = 'A'  # or 'B'

# The actual test can now use this in skipif
@pytest.mark.suiteA # include the test in Suite A
@pytest.mark.suiteB # include the test in Suite B
@pytest.mark.parametrize(
    "inputParameter",
    [(10), pytest.mark.skipif(sys._my_test_suite == 'A', reason='suite A only')(12)])])
def test_printInputParameter(inputParameter):
    print inputParameter

是的,在sys模块上设置一个属性确实是个不太优雅的解决办法,但在这种情况下,它是个简单的解决方案。

1

你走在正确的道路上。问题的一部分在于,你给测试函数同时加了 suiteA 和 suiteB 的标记,这样这个函数就被认为是两个测试套件的一部分。

你现在使用的参数标记方式在 '14 版本时可能有效(在 pytest 3 版本中也能用),但在最新的 pytest 版本(5.x)中就不行了。

这些例子使用的是最新的标记方式,这种方式至少在 pytest 3.x 版本中也能用。

根据 pytest 文档

使用 parametrize 时,应用一个标记会使其作用于每个单独的测试。不过,也可以将标记应用于单个测试实例:

import pytest


@pytest.mark.foo
@pytest.mark.parametrize(
    ("n", "expected"), [(1, 2), pytest.param(1, 3, marks=pytest.mark.bar), (2, 3)]
)
def test_increment(n, expected):
    assert n + 1 == expected

在上面的例子中,使用标记 foo 会让这个测试用所有参数(包括 bar)运行。使用标记 bar 则只会用标记的参数运行这个测试。

所以,对于你的例子,你可以这样做(请原谅我,除了标记外,我把所有名字都更新成了 PEP8 标准):

@pytest.mark.parametrize("input_parameter", [
        # use this input for Suite A and Suite B
        pytest.param(10, marks=[pytest.mark.suiteA, pytest.mark.suiteB]), 
        # use this input only for Suite B
        pytest.param(12, marks=pytest.mark.suiteB),
        # this input will run when no markers are specified
        (13),
]) 
def test_print_input_parameter(input_parameter):
    print(input_parameter)

你必须去掉函数上方的两个标记装饰器。你只需要这个参数装饰器。

根据评论,输入 10 被标记,以确保它只会在 suiteA 或 suiteB 中运行。如果调用了这两个中的任何一个,或者两个都调用,它就会执行。

输入 12 只和一个标记 suiteB 相关。只有在调用 suiteB 时,它才会执行。

我还添加了输入值 13,作为默认未标记测试运行的例子。像往常一样,这个不会在 suiteA 或 suiteB(或 suiteC 或任何其他标记过滤器)下执行,但如果没有指定标记,它会运行(其他的也是如此)。

另外,你也可以这样做:

@pytest.mark.suiteB # this function is a part of suiteB
@pytest.mark.parametrize("input_parameter", [
        # use this input for Suite A and Suite B
        pytest.param(10, marks=pytest.mark.suiteA), 
        # use this input only for Suite B
        (12),
        # this input is also part of Suite B thanks to the function decorator, as well as suiteC and suiteD
        pytest.param(13, marks=[pytest.mark.suiteC, pytest.mark.suiteD]),
]) 
def test_print_input_parameter(input_parameter):
    print(input_parameter)

在你最初的两个参数中,实际上你有一个完整的 suiteB 测试,只有一个参数是给 suiteA 的。

在这种情况下,函数装饰器会让整个测试在 suiteB 下运行。如果指定了 suiteA,只有 10 会被执行。

因为使用了函数装饰器,我自己设定的参数 13,和这个函数的所有参数一样,也属于 suiteB。我可以把它加到任意多个其他标记中,但函数装饰器确保这个测试会在 suiteB 下用所有参数运行。

这个替代方案在你的例子中是可行的,但如果你有任何不重叠的参数(例如 13 在 suiteB 下 被运行),你就必须像中间的例子那样逐个指定它们。

撰写回答