文章目录
前言
pytest-dependency
的主要用途是确保测试用例按照指定的依赖关系顺序执行。在一个复杂的测试套件中,某些测试用例可能依赖于其他测试用例的结果或状态。
pytest-dependency
允许明确地定义这些依赖关系,从而确保依赖项先执行,然后再执行依赖于此的测试用例。这意味着我们可以指定某些测试用例必须在其他测试用例成功执行后才能运行。
通过这种方式,我们可以确保测试的执行顺序与依赖关系得到正确的处理。
应用场景
- 有序的测试执行 :当测试用例之间存在逻辑依赖关系时,需要确保它们按照正确的顺序执行。
例如,一个测试用例可能设置了某些数据,而另一个测试用例依赖于这些数据。- 资源准备和清理 :在某些情况下,可能需要首先准备一些资源(如数据库连接、文件、网络服务等),并在所有相关测试完成后进行清理。
pytest-dependency
可以帮助你确保资源准备和清理的顺序正确。- 条件测试 :我们可能只想在满足特定条件时再执行某些测试用例。
pytest-dependency
可以基于其他测试用例的成功或失败来执行特定的测试。
插件安装
安装命令 :
pip install pytest-dependency
官方文档: https://pytest-dependency.readthedocs.io/en/latest/index.html
注意事项
误解依赖关系 :有些人可能误解了
depends
参数的含义,认为它定义了测试用例的执行顺序,但实际上它定义的是依赖关系,而不是执行顺序。执行顺序仍由
pytest
的内部机制决定,但pytest-dependency
插件会在必要时跳过或重新运行测试用例以满足依赖关系。错误的使用 :如果不正确地使用
pytest-dependency
,可能会导致测试用例之间的耦合度过高,难以维护和扩展。因此,应谨慎使用依赖关系,确保它们确实是必要的。
插件配置问题 :如果没有正确配置
pytest-dependency
插件,例如未安装插件或未正确地在pytest
命令中包含插件,那么依赖关系将不会生效。并发执行 :如果使用
pytest
的并发执行功能(如-n
参数),请确保依赖关系不会被打乱。
pytest-dependency
和 pytest 的并发执行功能可能不完全兼容,因此需要谨慎使用。测试隔离:尽量确保每个测试用例是独立的,不依赖于其他测试用例的结果。这样可以提高测试的健壮性和可复用性。
参数分析
@pytest.mark.dependency装饰器接受以下参数,其中最常用的是 depends:
depends : 这是一个字符串列表,指定了当前测试用例所依赖的其他测试用例的名称。
只有当这些依赖项都成功执行时,当前测试用例才会被执行。
scope : 这个参数定义了依赖关系的范围。
默认情况下,依赖关系是在整个测试会话中有效的。
如果设置为
"session"
, 它会覆盖默认行为。如果设置为
"function"
, 则每个函数都会创建一个新的依赖上下文。names: 这个参数允许你为依赖项指定一个自定义名称,这在某些情况下可能很有用,特别是当你有多个依赖项并希望使日志或报告更清晰时。
函数名称依赖实现方式
示例代码
python
import pytest
# 模拟数据库
db = {}
# 模拟注册
@pytest.mark.dependency()
def test_signup():
assert len(db.keys()) == 0
db.setdefault("username", "root")
db.setdefault("password", "1234")
# 模拟登录
@pytest.mark.dependency(depends=["test_signup"])
def test_login():
# 依赖于 test_signup 的测试用例
assert db.get("username") == "root"
assert db.get("password") == "1234"
被依赖的测试用例执行成功结果
被依赖的测试用例执行失败结果
类下函数路径实现方式
示例代码
python
import pytest
class TestCase:
@pytest.mark.dependency()
def test_01(self):
assert False
@pytest.mark.dependency(depends=["TestCase::test_01"])
def test_02(self):
assert False
执行结果
通过设置别名指定依赖
示例代码
python
import pytest
# 模拟数据库
db = {}
# 模拟注册
@pytest.mark.dependency(name="1")
def test_signup():
assert len(db.keys()) == 0
db.setdefault("username", "root")
db.setdefault("password", "1234")
def test_03():
assert True
# 模拟登录
@pytest.mark.dependency(depends=["1"])
def test_login():
# 依赖于 test_signup 的测试用例
assert db.get("username") == "root"
assert db.get("password") == "1234"
class TestCase:
@pytest.mark.dependency("name")
def test_01(self):
assert False
@pytest.mark.dependency(depends="name")
def test_02(self):
assert False
执行结果
定义依赖范围
从官方说明中我们看到scope可以接受四种参数定义的类型:
- session
- package
- module
- class
作用于类
scope="class"
- 作用于所属类,外部类不会被关联
示例代码
python
import pytest
class TestCase1:
@pytest.mark.dependency()
def test_01(self):
assert False
class TestCase2:
@pytest.mark.dependency(scope="class")
def test_01(self):
assert True
@pytest.mark.dependency(depends=["test_01"], scope="class")
def test_02(self):
assert True
执行结果
作用于模块
scope="module"
- 不传递
scope
参数,即默认参数是module
,作用于当前py文件
。- 只会查找当前文件的符合条件的文件名,类里同名的方法不会被依赖
示例代码
python
import pytest
@pytest.mark.dependency()
@pytest.mark.xfail(reason="模拟失败")
def test_fail():
print("进入module::test_fail函数")
assert False
class TestCase1:
@pytest.mark.dependency()
def test_01(self):
assert False
class TestCase2:
@pytest.mark.dependency()
def test_fail(self):
print("进入TestCase2::test_fail函数")
assert True
@pytest.mark.dependency(depends=["test_fail"])
def test_02(self):
assert True
执行结果
作用于包
scope="package"
- 作用于当前目录同级的依赖函数,跨目录无法找到依赖的函数。
test_case_01.py
示例代码
python
import pytest
class TestCase1:
@pytest.mark.dependency(scope="class")
def test_01(self):
print('测试用例01')
assert 1 == 1
@pytest.mark.dependency(depends=['test_01'], scope="class")
def test_02(self):
print('测试用例02依赖于测试用例01')
assert True
test_case_02.py
示例代码
python
import pytest
class TestCase2:
@pytest.mark.dependency(
depends=['testcase/test_case_01.py::TestCase1::test_02'],
scope="package"
)
def test_03(self):
print('测试用例03依赖于统计目录下test_case_01.py的02')
assert True
执行结果
作用于会话
scope="session"
- 作用域全局,可跨目录调用。但被依赖的用例必须先执行,否则用例会执行跳过!
testcase/test_case_01.py
示例代码
python
import pytest
class TestCase1:
@pytest.mark.dependency(scope="class")
def test_01(self):
print('测试用例01')
assert 1 == 1
@pytest.mark.dependency(depends=['test_01'], scope="class")
def test_02(self):
print('测试用例02依赖于测试用例01')
assert True
testcase/test_case_02.py
示例代码
python
import pytest
class TestCase2:
@pytest.mark.dependency(
depends=['testcase/test_case_01.py::TestCase1::test_02'],
scope="package"
)
def test_03(self):
print('测试用例03依赖于统计目录下test_case_01.py的02')
assert True
testcase2/test_case_03.py
示例代码
python
import pytest
class TestCase3:
@pytest.mark.dependency(
depends=['testcase/test_case_02.py::TestCase2::test_03'],
scope="session"
)
def test_04(self):
print('测试用例04依赖于非同级目录下test_case_02.py的03')
assert True
执行结果
拓展-非常重要
- 当使用
pytest.mark.parametrize
对测试用例进行参数化时,每个参数组合都会产生一个独立的测试用例实例 ,每个实例都有一个唯一的节点ID(nodeid),这个ID包含了测试用例的路径和参数值。- 因此,如果依赖的上下文测试用例使用了参数化 ,那么仅仅通过测试函数的方法名来建立依赖关系是不够的,因为同一个测试函数可能会有多个不同的实例,每个实例的节点ID都是唯一的。
- 为了解决这个问题,
pytest-dependency
插件支持使用节点ID来建立依赖关系,而不仅仅是测试函数的方法名。你可以通过传递节点ID数组来指定依赖关系,而不是简单地传递方法名。
示例代码
python
import pytest
@pytest.mark.dependency()
@pytest.mark.parametrize('data', [1])
def test_a(data):
# data = 1
assert data == 1
@pytest.mark.dependency(depends=['test_a'])
def test_d():
assert True
@pytest.mark.dependency(depends=['test_d'])
def test_b():
assert 'ooo' == 'ooo'
@pytest.mark.dependency(depends=['test_b'])
def test_c():
assert 'lll' is 'lll'
执行结果-未指定参数化改变的node信息
执行结果-指定参数化改变的node信息