pytest: Unexpected order of tests using parameterized fixtures
Consider following code:
import pytest
@pytest.fixture(scope="function", params=(1, 2, 3))
def f1(request):
pass
@pytest.fixture(scope="function", params=('a', 'b', 'c'))
def f2(request):
pass
def test(f1, f2):
pass
The order of the tests meets my expectations:
============================= test session starts =============================
platform win32 -- Python 2.7.12, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- c:\users\rbierbasz\.virtualenvs\core\scripts\python.exe
cachedir: .cache
rootdir: C:\Users\rbierbasz\repos\galaxy-core, inifile:
collected 9 items
test_order.py::test[1-a] PASSED
test_order.py::test[1-b] PASSED
test_order.py::test[1-c] PASSED
test_order.py::test[2-a] PASSED
test_order.py::test[2-b] PASSED
test_order.py::test[2-c] PASSED
test_order.py::test[3-a] PASSED
test_order.py::test[3-b] PASSED
test_order.py::test[3-c] PASSED
========================== 9 passed in 0.04 seconds ===========================
But when I changed fixtures’ scope from function
to module
:
import pytest
@pytest.fixture(scope="module", params=(1, 2, 3))
def f1(request):
pass
@pytest.fixture(scope="module", params=('a', 'b', 'c'))
def f2(request):
pass
def test(f1, f2):
pass
it seems to get random:
============================= test session starts =============================
platform win32 -- Python 2.7.12, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- c:\users\rbierbasz\.virtualenvs\core\scripts\python.exe
cachedir: .cache
rootdir: C:\Users\rbierbasz\repos\galaxy-core, inifile:
collected 9 items
test_order.py::test[1-a] PASSED
test_order.py::test[1-b] PASSED
test_order.py::test[2-b] PASSED
test_order.py::test[2-a] PASSED
test_order.py::test[2-c] PASSED
test_order.py::test[1-c] PASSED
test_order.py::test[3-c] PASSED
test_order.py::test[3-b] PASSED
test_order.py::test[3-a] PASSED
========================== 9 passed in 0.04 seconds ===========================
The same happens for class
and session
scopes. It leads to fixtures being set up and torn down more times than necessary.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 1
- Comments: 23 (13 by maintainers)
@kchomski-reef thanks for the great insight. I haven’t checked
reorder_items
code yet, but your reasoning seems correct. I only think that the assumptions on which this functionality is based are naive - that all paremetrized fixtures have the same weight. In real worldf1
fixture from my example can have much more expensive setup/teardown, or there can be more fixtures depending on it. It would be great if I could control the order somehow. I tried indirect parametrization:But it breaks fixtures’ scopes (#570). The only working solutions for me now is moving one parametrized fixture to higher scope:
But it seems hacky and it’s not scalable. Is there any way of achieving it? Some hook maybe?
Good point to have a new issue rather than stuck in a closed ticket. #3393 created.
@kchomski-reef thanks for the interest.
You are half right: the purpose is to reorder test itemswithin a scope to minimize fixture setup/teardown, when those fixtures are parametrized.
For example, consider this session fixture:
Consider this test file:
If we don’t do any reordering, the tests would execute in this order:
IOW, this would cause
session_fix
to be created with parameter1
fortest_uses_fix_1
, then be destroyed so a newsession_fix
can be created with parameter2
and executetest_uses_fix_1
again with the new fixture. The same happens fortest_uses_fix_2
and for every other test which uses those fixtures.A key fact to understand all this and that is not clear in the documentation is that at any given time only a single instance of
session_fix
can exist, that’s why it needs to be destroyed between each test function call which parametrizes it.The reorder algorithm thus tries to reorder the tests to minimize fixture setup/teardown:
This order now allow us to create
session_fix(1)
once, executetest_uses_fix_1
andtest_uses_fix_2
with that fixture, then createsession_fix(2)
and execute the tests, and so on.The same is valid for the other scopes other than function.
Let me know if something is not clear on my explanation.