对于 pytest 的用例依赖管理,可以使用 pytest-dependency
插件。该插件提供了更多的依赖管理功能,使你能够更灵活地定义和控制测试用例之间的依赖关系。
Using pytest-dependency — pytest-dependency 0.5.1 documentation
安装 pytest-dependency 插件:
pip install pytest-dependency
基本使用
依赖方法和被依赖方法都需要使用装饰器 @pytest.mark.dependency
在依赖方法装饰器参数列表里填写依赖的用例名称列表
import pytest
@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
assert False
@pytest.mark.dependency()
def test_b():
pass
@pytest.mark.dependency(depends=["test_a"])
def test_c():
pass
@pytest.mark.dependency(depends=["test_b"])
def test_d():
pass
@pytest.mark.dependency(depends=["test_b", "test_c"])
def test_e():
pass
执行结果:2个通过 3个忽略
被依赖的用例执行失败后,依赖的用例不执行,
a执行失败,所以c和e都被忽略了,a也被忽略了。
为测试用例命名
使用name为测试用例命名,在依赖调用列表可以使用name调用。
import pytest
@pytest.mark.dependency(name="a")
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
assert False
@pytest.mark.dependency(name="b")
def test_b():
pass
@pytest.mark.dependency(name="c", depends=["a"])
def test_c():
pass
@pytest.mark.dependency(name="d", depends=["b"])
def test_d():
pass
@pytest.mark.dependency(name="e", depends=["b", "c"])
def test_e():
pass
测试类中的测试方法
在 pytest 中,可以将测试用例分组到类中。对于测试类中的方法标记依赖关系的方式与简单的测试函数相同。在下面的示例中,我们定义了两个测试类。每个测试类的工作方式与之前的示例相同:
```python
import pytest
@pytest.mark.dependency
class TestClassA:
def test_a(self):
assert False
@pytest.mark.dependency(depends=["TestClassA::test_a"])
def test_b(self):
assert True
@pytest.mark.dependency
class TestClassB:
def test_c(self):
assert False
@pytest.mark.dependency(depends=["TestClassB::test_c"])
def test_d(self):
assert True
```
在这个示例中,我们定义了两个测试类 `TestClassA` 和 `TestClassB`。每个测试类中的方法都用 `@pytest.mark.dependency` 进行了标记,以指定它们的依赖关系。依赖关系通过传递类名和方法名来指定,格式为 `"TestClass::test_method"`。
这样,你就可以使用测试类来组织和管理测试用例,并使用 `@pytest.mark.dependency` 来标记它们之间的依赖关系。在运行测试时,pytest 将按照定义的依赖关系顺序执行测试方法。
参数化测试用例
import pytest
@pytest.mark.parametrize("x,y", [
pytest.param(0, 0, marks=pytest.mark.dependency(name="a1")),
pytest.param(0, 1, marks=[pytest.mark.dependency(name="a2"),
pytest.mark.xfail]),
pytest.param(1, 0, marks=pytest.mark.dependency(name="a3")),
pytest.param(1, 1, marks=pytest.mark.dependency(name="a4"))
])
def test_a(x,y):
assert y <= x
@pytest.mark.parametrize("u,v", [
pytest.param(1, 2, marks=pytest.mark.dependency(name="b1",
depends=["a1", "a2"])),
pytest.param(1, 3, marks=pytest.mark.dependency(name="b2",
depends=["a1", "a3"])),
pytest.param(1, 4, marks=pytest.mark.dependency(name="b3",
depends=["a1", "a4"])),
pytest.param(2, 3, marks=pytest.mark.dependency(name="b4",
depends=["a2", "a3"])),
pytest.param(2, 4, marks=pytest.mark.dependency(name="b5",
depends=["a2", "a4"])),
pytest.param(3, 4, marks=pytest.mark.dependency(name="b6",
depends=["a3", "a4"]))
])
def test_b(u,v):
pass
@pytest.mark.parametrize("w", [
pytest.param(1, marks=pytest.mark.dependency(name="c1",
depends=["b1", "b2", "b6"])),
pytest.param(2, marks=pytest.mark.dependency(name="c2",
depends=["b2", "b3", "b6"])),
pytest.param(3, marks=pytest.mark.dependency(name="c3",
depends=["b2", "b4", "b6"]))
])
def test_c(w):
pass
运行时依赖
有时,测试实例的依赖关系太复杂,无法使用 pytest.mark.dependency() 标记在运行之前明确地进行公式化。在运行时编译测试的依赖关系列表可能更容易。在这种情况下,pytest_dependency.depends() 函数非常有用。考虑以下示例:
```python
import pytest
from pytest_dependency import depends
@pytest.mark.dependency
def test_a():
assert False
@pytest.mark.dependency
def test_b():
depends(test_a())
assert True
```
在这个示例中,我们使用 pytest_dependency.depends() 函数定义了 test_b() 依赖于 test_a() 的关系。这样,我们可以在运行时根据 test_b() 的需要动态地编译依赖关系列表。
使用 pytest_dependency.depends() 函数时,只需将需要依赖的测试方法作为函数参数传递给它即可。
指明作用范围
scope的默认范围是module,所以基本使用的例子也可以写为如下,
实现效果没有区别,只是指明了范围
import pytest
@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
assert False
@pytest.mark.dependency()
def test_b():
pass
@pytest.mark.dependency(depends=["test_a"], scope='module')
def test_c():
pass
@pytest.mark.dependency(depends=["test_b"], scope='module')
def test_d():
pass
@pytest.mark.dependency(depends=["test_b", "test_c"], scope='module')
def test_e():
pass
跨模块需要指明范围为session
如果一个用例依赖的另一个用例在不同的模块,依赖的用例的scope必须是session或者是package。
# test_mod_01.py
import pytest
@pytest.mark.dependency()
def test_a():
pass
@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_b():
assert False
@pytest.mark.dependency(depends=["test_a"])
def test_c():
pass
class TestClass(object):
@pytest.mark.dependency()
def test_b(self):
pass
# test_mod_02.py
import pytest
@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
assert False
@pytest.mark.dependency(
depends=["tests/test_mod_01.py::test_a", "tests/test_mod_01.py::test_c"],
scope='session'
)
def test_e():
pass
@pytest.mark.dependency(
depends=["tests/test_mod_01.py::test_b", "tests/test_mod_02.py::test_e"],
scope='session'
)
def test_f():
pass
@pytest.mark.dependency(
depends=["tests/test_mod_01.py::TestClass::test_b"],
scope='session'
)
def test_g():
pass
范围为class
测试依赖关系也可以在类范围的级别上定义。这仅适用于测试类中的方法,并将依赖限制为同一类中的其他测试方法。
import pytest
@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
assert False
class TestClass1(object):
@pytest.mark.dependency()
def test_b(self):
pass
class TestClass2(object):
@pytest.mark.dependency()
def test_a(self):
pass
@pytest.mark.dependency(depends=["test_a"])
def test_c(self):
pass
@pytest.mark.dependency(depends=["test_a"], scope='class')
def test_d(self):
pass
@pytest.mark.dependency(depends=["test_b"], scope='class')
def test_e(self):
pass
一组测试使用fixture
pytest 在测试用例中对 fixture 实例进行自动分组。如果有一组测试用例,并且需要针对每个测试用例运行一系列的测试,这将非常有用。
例如:
```python
import pytest
# 定义一个测试用例
@pytest.fixture(params=[1, 2, 3])
def test_case(request):
return request.param
# 运行多次测验
def test_my_tests(test_case):
assert test_case > 0
def test_other_tests(test_case):
assert test_case < 10
```
在这个示例中,我们定义了一个名为 `test_case` 的 fixture,它使用 `@pytest.fixture` 装饰器和 `params` 参数来定义一个包含多个测试用例的列表。然后,我们使用 `test_case` fixture 来运行多个测试方法 `test_my_tests` 和 `test_other_tests`。pytest 会自动将这些测试方法与每个测试用例进行匹配,并为每个测试用例运行对应的测试方法。
通过这种方式,我们可以轻松地为每个测试用例执行一系列的测试,而不需要手动为每个测试用例编写独立的测试方法。
使用夹具为用例分组
pytest具有按夹具实例自动分组测试的功能。如果存在一组测试用例,并且对于每个测试用例都需要运行一系列的测试,这一特性尤其有用。
import pytest
from pytest_dependency import depends
@pytest.fixture(scope="module", params=range(1,10))
def testcase(request):
param = request.param
return param
@pytest.mark.dependency()
def test_a(testcase):
if testcase % 7 == 0:
pytest.xfail("deliberate fail")
assert False
@pytest.mark.dependency()
def test_b(request, testcase):
depends(request, ["test_a[%d]" % testcase])
pass
if __name__ == '__main__':
pytest.main(["-sv"])
因为test_a[7]执行失败,所以test_b[7]被跳过。
如果多个测试方法依赖于一个测试方法,则可以把pytest_dependency.depends()调用单独写一个fixture
import pytest
from pytest_dependency import depends
@pytest.fixture(scope="module", params=range(1,10))
def testcase(request):
param = request.param
return param
@pytest.fixture(scope="module")
def dep_testcase(request, testcase):
depends(request, ["test_a[%d]" % testcase])
return testcase
@pytest.mark.dependency()
def test_a(testcase):
if testcase % 7 == 0:
pytest.xfail("deliberate fail")
assert False
@pytest.mark.dependency()
def test_b(dep_testcase):
pass
@pytest.mark.dependency()
def test_c(dep_testcase):
pass
test_b[7]和test_c[7] 会被跳过,因为test_a[7]失败了。
依赖参数化测试方法
如果一个测试同时依赖于一个参数化测试的所有实例,逐个列出它们在 pytest.mark.dependency() 标记中可能不是最佳解决方案。但是可以根据参数值动态地编译这些列表,如以下示例所示:
import pytest
def instances(name, params):
def vstr(val):
if isinstance(val, (list, tuple)):
return "-".join([str(v) for v in val])
else:
return str(val)
return ["%s[%s]" % (name, vstr(v)) for v in params]
params_a = range(17)
@pytest.mark.parametrize("x", params_a)
@pytest.mark.dependency()
def test_a(x):
if x == 13:
pytest.xfail("deliberate fail")
assert False
else:
pass
@pytest.mark.dependency(depends=instances("test_a", params_a))
def test_b():
pass
params_c = list(zip(range(0,8,2), range(2,6)))
@pytest.mark.parametrize("x,y", params_c)
@pytest.mark.dependency()
def test_c(x, y):
if x > y:
pytest.xfail("deliberate fail")
assert False
else:
pass
@pytest.mark.dependency(depends=instances("test_c", params_c))
def test_d():
pass
params_e = ['abc', 'def']
@pytest.mark.parametrize("s", params_e)
@pytest.mark.dependency()
def test_e(s):
if 'e' in s:
pytest.xfail("deliberate fail")
assert False
else:
pass
@pytest.mark.dependency(depends=instances("test_e", params_e))
def test_f():
pass
test_b, test_d, and test_f will be skipped because they depend on all instances of test_a, test_c, and test_e respectively, but test_a[13], test_c[6-5], and test_e[def] fail. The list of the test instances is compiled in the helper function instances().
缺点
依赖用例执行顺序
这个库非常依赖用例的执行顺序,如在执行被依赖方法时,发现被依赖的方法未被执行,依赖方法会被忽略。
import pytest
@pytest.mark.dependency()
def test_b():
pass
@pytest.mark.dependency(depends=["test_a"])
def test_c():
pass
@pytest.mark.dependency(depends=["test_b"])
def test_d():
pass
@pytest.mark.dependency(depends=["test_b", "test_c"])
def test_e():
pass
@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
assert True
if __name__ == '__main__':
pytest.main(["-sv"])
这个例子最后执行a,但c,e仍被忽略了。