pytest中如何控制测试用例的执行顺序?
- 2025-03-13 09:22:00
- admin 原创
- 15
问题描述:
我在一个目录中有两个文件。其中一个文件中有一个长时间运行的测试用例,它会生成一些输出。另一个文件中有一个读取该输出的测试用例。
如何确保两个测试用例的执行顺序正确?除了以正确的顺序将测试用例放在同一个文件中之外,还有其他选择吗?
解决方案 1:
一般来说,你可以使用 pytest明确指定的钩子来配置其基本上任何部分的行为。
就您而言,您需要“pytest_collection_modifyitems”钩子,它可以让您重新排序收集的测试。
话虽如此,但测试排序似乎应该更容易——毕竟这是 Python!所以我写了一个用于测试排序的插件:“pytest-ordering”。查看文档或从pypi安装。现在我建议使用@pytest.mark.first
和@pytest.mark.second
或其中一个@pytest.mark.order#
标记,但我对更有用的 API 有一些想法。欢迎提出建议 :)
编辑:pytest-ordering 目前似乎已被废弃,您也可以查看pytest-order(作者对原始项目的一个分支)。
编辑2order
:在pytest-order中,只支持一个标记( ),并且提到的例子将是@pytest.mark.order("first")
、、@pytest.mark.order("second")
或@pytest.mark.order(#)
(其中#是任意数字)。
解决方案 2:
也许您可以考虑使用依赖项pytest 插件,您可以在其中轻松设置测试依赖项。
请小心——评论表明这并不适合所有人。
@pytest.mark.dependency()
def test_long():
pass
@pytest.mark.dependency(depends=['test_long'])
def test_short():
pass
这种方式test_short
只有test_long
成功时才会执行,并且强制执行顺序。
解决方案 3:
正如@Frank T在接受的答案中所指出的,pytest_collection_modifyitems
hook钩子允许修改收集测试的顺序 ( items
)。这种方法的优点是不需要任何第三方库。
该答案中已经提供了有关如何按测试类强制执行测试用例执行顺序的完整示例。
但是,在这种情况下,您似乎希望按测试模块(即.py
测试所在的文件)强制执行执行顺序。以下修改将允许您这样做:
# conftest.py
def pytest_collection_modifyitems(items):
"""Modifies test items in place to ensure test modules run in a given order."""
MODULE_ORDER = ["tests.test_b", "tests.test_c", "tests.test_a"]
module_mapping = {item: item.module.__name__ for item in items}
sorted_items = items.copy()
# Iteratively move tests of each module to the end of the test queue
for module in MODULE_ORDER:
sorted_items = [it for it in sorted_items if module_mapping[it] != module] + [
it for it in sorted_items if module_mapping[it] == module
]
items[:] = sorted_items
将上面的代码片段放入其中,conftest.py
将默认的字母测试执行顺序test_a
-> test_b
->替换test_c
为test_b
-> test_c
-> test_a
。模块可以位于不同的测试子目录中,模块内的测试顺序保持不变。
解决方案 4:
Pytest 的 Fixtures 可用于对测试进行排序,其方式与对 Fixtures 的创建进行排序的方式类似。虽然这种方式并不常见,但它充分利用了您可能已经掌握的 Fixture 系统知识,不需要单独的软件包,也不太可能被 pytest 插件改变。
@pytest.fixture(scope='session')
def test_A():
pass
@pytest.mark.usefixtures('test_A')
def test_B():
pass
如果有多个测试依赖于 test_A,则范围会阻止对其进行多次调用。
解决方案 5:
重要的是要记住,在尝试修复 pytest 排序“问题”时,按照指定的顺序运行测试似乎是 pytest 的默认行为。
事实证明,我的测试顺序不对,是因为其中一个软件包 - pytest-dependency
、、pytest-depends
。pytest-order
一旦我用 卸载它们pip uninstall package_name
,问题就消失了。看起来它们有副作用
解决方案 6:
纯 pytest 解决方案是使用pytest.mark.parametrize
,向其传递要调用的子函数的任意排序列表。在本问题中提出的问题中,您必须从文件(或其中一个文件)导入测试函数。无论哪种方式,您都需要一个__init__.py
文件。
这是一个最小的工作示例,不含导入:
tests/
- __init__.py (empty)
- tests_that_need_ordering.py
- test_run_ordered.py
# tests_that_need_ordering.py
def C():
assert False
def A():
assert False
def B():
pass
# test_run_ordered.py
import pytest
from .tests_that_need_ordering import A, B, C
@pytest.mark.parametrize("func", [C, B, A])
def test_things(func):
func()
请注意
子函数文件名不以
test
- 开头或结尾,否则它将被 pytest 直接拾取。同样,如果 pytest 查看,则不会拾取其中的测试名称。函数定义顺序为 CA B。如果它们是文件
test_*
中的普通函数test_*
,pytest 会按照此顺序调用它们。精心设计的参数化顺序是CB A。
详细运行的输出-vv
表明它们按照 CBA 的故意顺序运行,并且故障按预期报告:
tests/test_run_ordered.py::test_things[C] FAILED
tests/test_run_ordered.py::test_things[B] PASSED
tests/test_run_ordered.py::test_things[A] FAILED
为了方便以后参考,我使用 Python 3.10.13、pytest-7.4.2 执行了此操作
解决方案 7:
对我来说,修复测试执行顺序的最简单方法是将它们用作固定装置,按照设计顺序执行。
@pytest.fixture()
def test_a():
print("running test a first")
def test_b(test_a):
print("running test b after test a")
将测试依赖项标记为固定装置,并将其作为参数传递给依赖项。
解决方案 8:
主要文件:
import functools
import pytest
from demo import test_foo,test_hi
def check_depends(depends):
try:
for dep in depends:
dep()
except Exception as e:
return dep
else:
return True
def pytest_depend(depends):
def pytest_depend_decorator(func):
stat = check_depends(depends)
if stat is True:
return func
else:
return pytest.mark.skip(True, reason="%s[skip] --> %s[Failed]" % (func.__name__, stat.__name__))(func)
return pytest_depend_decorator
@pytest_depend([test_foo,test_hi])
def test_bar():
pass
@pytest_depend([test_foo,test_hi])
def test_bar2():
pass
演示.py:
def test_hi():
pass
def test_foo():
assert False
平台 Linux——Python 3.5.2、pytest-3.8.2、py-1.6.0、pluggy-0.7.1——/usr/bin/python3
pytest-vrsx./plugin.py
解决方案 9:
函数导入顺序也决定了执行顺序。在下面的用例中,可重用测试被导入到模块中,导入顺序优先于调用顺序。
from livy.reusable_livy_tests import (
test_delete_livy_session,
test_create_livy_session,
test_get_livy_session,
test_livy_session_started,
test_computation_of_a_statement,
)
test_create_livy_session
test_get_livy_session
test_livy_session_started
test_computation_of_a_statement
test_delete_livy_session
在上面的示例中,test_delete_livy_session 首先运行,因为它是首先导入的,尽管最后被调用。我今天在 python 版本 3.6.8、pytest 版本 7.0.0 中观察到了这一点。
解决方案 10:
利用pytest-randomly插件中的“--randomly-dont-reorganize”选项或“-p no:randomly”,这将按照您在模块中提到的顺序运行测试。
模块:
import pytest
def test_three():
assert True
def test_four():
assert True
def test_two():
assert True
def test_one():
assert True
执行:
(tmp.w95BqE188N) rkalaiselvan@dev-rkalaiselvan:~/$ py.test --randomly-dont-reorganize test_dumm.py
======================================================================== test session starts ========================================================================
platform linux2 -- Python 2.7.12, pytest-3.10.1, py-1.5.4, pluggy-0.7.1 -- /tmp/tmp.w95BqE188N/bin/python2
cachedir: .pytest_cache
Using --randomly-seed=1566829391
rootdir: /home/rkalaiselvan, inifile: pytest.ini
plugins: randomly-1.2.3, timeout-1.3.1, cov-2.6.0, mock-1.10.0, ordering-0.6
collected 4 items
test_dumm.py::test_three PASSED
test_dumm.py::test_four PASSED
test_dumm.py::test_two PASSED
test_dumm.py::test_one PASSED
(tmp.w95BqE188N) rkalaiselvan@dev-rkalaiselvan:~/$ py.test -p no:randomly test_dumm.py
======================================================================== test session starts ========================================================================
platform linux2 -- Python 2.7.12, pytest-3.10.1, py-1.5.4, pluggy-0.7.1 -- /tmp/tmp.w95BqE188N/bin/python2
cachedir: .pytest_cache
Using --randomly-seed=1566829391
rootdir: /home/rkalaiselvan, inifile: pytest.ini
plugins: randomly-1.2.3, timeout-1.3.1, cov-2.6.0, mock-1.10.0, ordering-0.6
collected 4 items
test_dumm.py::test_three PASSED
test_dumm.py::test_four PASSED
test_dumm.py::test_two PASSED
test_dumm.py::test_one PASSED
解决方案 11:
@swimmer 的回答很好。这是完善的代码。
确保测试项目首先运行
# conftest.py
def pytest_collection_modifyitems(items):
"""test items come to first"""
run_first = ["tests.test_b", "tests.test_c", "tests.test_a"]
modules = {item: item.module.__name__ for item in items}
items[:] = sorted(
items, key=lambda x: run_first.index(modules[x]) if modules[x] in run_first else len(items)
)
确保测试项目最后运行
# conftest.py
def pytest_collection_modifyitems(items):
"""test items come to last"""
run_last = ["tests.test_b", "tests.test_c", "tests.test_a"]
modules = {item: item.module.__name__ for item in items}
items[:] = sorted(
items, key=lambda x: run_last.index(modules[x]) if modules[x] in run_last else -1
)