Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- I took pytest from `features` branch because it has `--setup-plan` flag
- that makes it display the order of setup and teardown of fixtures. This
- information is quite useful for understanding what happens in this
- issue.
- I added the following lines to `Metafunc.parametrize` in
- `_pytest/python.py` to observe parametrization (I used python 2 for
- consistency with the examples from the issue):
- diff --git a/_pytest/python.py b/_pytest/python.py
- index 37151d9..465954f 100644
- --- a/_pytest/python.py
- +++ b/_pytest/python.py
- @@ -763,6 +763,8 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
- It will also override any fixture-function defined scope, allowing
- to set a dynamic scope using test context or configuration.
- """
- + print 'parametrize {} argnames={} indirect={} scope={}'.format(
- + self.function.__name__, argnames, indirect, scope)
- # individual parametrized argument sets can be wrapped in a series
- # of markers in which case we unwrap the values and apply the mark
- Here's the original test (SRC1):
- import pytest
- @pytest.yield_fixture(scope="session",
- params=["big mac", "whopper"])
- def burger(request):
- yield request.param
- @pytest.yield_fixture(scope="function",
- params=["curlyfries", "regularfries"])
- def fries(request):
- yield request.param
- def test_burgers(burger):
- print "test_burgers: {0}".format(burger)
- def test_combo1(burger, fries):
- print("test_combo1: {0}{1}".format(burger, fries))
- It produces the following output (OUT1):
- $ pytest --setup-only -s test_fastfood.py
- ...
- parametrize test_burgers argnames=burger indirect=True scope=session
- parametrize test_combo1 argnames=burger indirect=True scope=session
- parametrize test_combo1 argnames=fries indirect=True scope=function
- collected 6 items
- test_fastfood.py
- SETUP S burger[big mac]
- test_fastfood.py::test_burgers[big mac] (fixtures used: burger)
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[big mac-curlyfries] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[big mac-regularfries] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN S burger[big mac]
- SETUP S burger[whopper]
- test_fastfood.py::test_burgers[whopper] (fixtures used: burger)
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[whopper-curlyfries] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[whopper-regularfries] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN S burger[whopper]
- We can see that `parametrize` is called with `scope` set to `"session"`
- and `indirect` set to `True`. As expected, the tests are ordered in a
- resonable way and the session scoped fixture is created twice: once
- for each value of the parameter.
- Then I removed the parameters from fixture decorator and added the
- `pytest_generate_tests` function that parametrizes `burger` fixture
- programmatically to get the second example from the issue (SRC2):
- import pytest
- def pytest_generate_tests(metafunc):
- if "burger" in metafunc.fixturenames:
- metafunc.parametrize("burger", argvalues=["big mac", "whopper"])
- @pytest.yield_fixture(scope="session")
- def burger(request):
- yield request.param
- # the rest is the same as before
- This produces the following output (OUT2):
- $ pytest --setup-only -s test_fastfood.py
- ...
- parametrize test_burgers argnames=burger indirect=False scope=None
- parametrize test_combo1 argnames=burger indirect=False scope=None
- parametrize test_combo1 argnames=fries indirect=True scope=function
- collected 6 items
- test_fastfood.py
- SETUP F burger[big mac]
- test_fastfood.py::test_burgers[big mac] (fixtures used: burger)
- TEARDOWN F burger[big mac]
- SETUP F burger[whopper]
- test_fastfood.py::test_burgers[whopper] (fixtures used: burger)
- TEARDOWN F burger[whopper]
- SETUP F burger[big mac]
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[big mac-curlyfries] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- TEARDOWN F burger[big mac]
- SETUP F burger[big mac]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[big mac-regularfries] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN F burger[big mac]
- SETUP F burger[whopper]
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[whopper-curlyfries] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- TEARDOWN F burger[whopper]
- SETUP F burger[whopper]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[whopper-regularfries] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN F burger[whopper]
- We can see that `burger` fixture becomes function scope (the `F` letter
- after `SETUP` and `TEARDOWN`) and is now created and destroyed for each
- test item. The order of the tests changes but it makes sense given that
- the fixture is now function scoped.
- Above we can see that the scope parameter of `metafunc.parametrize` is
- `None` for `burger` fixtures. Let's set it to `"session"` (SRC3):
- def pytest_generate_tests(metafunc):
- if "burger" in metafunc.fixturenames:
- metafunc.parametrize("burger", argvalues=["big mac", "whopper"],
- scope="session")
- This produces the following output (OUT3):
- $ pytest --setup-only -s test_fastfood.py
- ...
- parametrize test_burgers argnames=burger indirect=False scope=session
- parametrize test_combo1 argnames=burger indirect=False scope=session
- parametrize test_combo1 argnames=fries indirect=True scope=function
- collected 6 items
- test_fastfood.py
- SETUP S burger[big mac]
- test_fastfood.py::test_burgers[big mac] (fixtures used: burger)
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[big mac-curlyfries] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- TEARDOWN S burger[big mac]
- SETUP S burger[whopper]
- test_fastfood.py::test_burgers[whopper] (fixtures used: burger)
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[big mac-regularfries] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN S burger[whopper]
- SETUP S burger[whopper]
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[whopper-curlyfries] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- TEARDOWN S burger[whopper]
- SETUP S burger[whopper]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[whopper-regularfries] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN S burger[whopper]
- This seems strange, but actually it sort of makes sense. Our
- parametrization of the tests is direct, so what happens is that a
- separate session scoped virtual fixture called `burger` is created for
- each test function. The test suite uses two separate session scoped
- fixtures both called `burger` and that confuses the sorting of the tests
- as well as the code that does `--setup-plan`. I don't really see the
- purpose of this setup, so I wouldn't necessarily call this a bug.
- Let's make parametrization indirect to bring back some sanity (SRC4):
- def pytest_generate_tests(metafunc):
- if "burger" in metafunc.fixturenames:
- metafunc.parametrize("burger", argvalues=["big mac", "whopper"],
- indirect=True, scope="session")
- Run pytest again (OUT4):
- $ pytest --setup-only -s test_fastfood.py
- ...
- parametrize test_burgers argnames=burger indirect=True scope=session
- parametrize test_combo1 argnames=burger indirect=True scope=session
- parametrize test_combo1 argnames=fries indirect=True scope=function
- collected 6 items
- test_fastfood.py
- SETUP S burger[big mac]
- test_fastfood.py::test_burgers[big mac] (fixtures used: burger)
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[big mac-curlyfries] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[big mac-regularfries] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN S burger[big mac]
- SETUP S burger[whopper]
- test_fastfood.py::test_burgers[whopper] (fixtures used: burger)
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[whopper-curlyfries] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[whopper-regularfries] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN S burger[whopper]
- This is the same ouput as OUT1, so sorting now works correctly.
- Another variation worth noting is parametrization via
- `pytest.mark.parametrize` decorator (SRC5):
- import pytest
- @pytest.yield_fixture(scope="session")
- def burger(request):
- yield request.param
- @pytest.yield_fixture(scope="function",
- params=["curlyfries", "regularfries"])
- def fries(request):
- yield request.param
- @pytest.mark.parametrize("burger", ["big mac", "whopper"])
- def test_burgers(burger):
- print "\ntest_burgers: {0}".format(burger)
- @pytest.mark.parametrize("burger", ["big mac", "whopper"])
- def test_combo1(burger, fries):
- print("\ntest_combo1: {0}{1}".format(burger, fries))
- This, quite predictably, produces OUT2. Adding `scope="session"` to
- the arguments of `parametrize` brings us to OUT3 and further adding
- `indirect=True` brings us to OUT4/1. Everything is consistent here.
- Just for completeness of the picture let's do another experiment (SRC6):
- @pytest.mark.parametrize("burger", ["big mac", "whopper"], indirect=True)
- This mean that we parametrize our session scoped `burger` fixture in the
- function scope (whatever that's supposed to mean). Here's the output
- (OUT6):
- parametrize test_burgers argnames=burger indirect=True scope=None
- parametrize test_combo1 argnames=fries indirect=True scope=function
- parametrize test_combo1 argnames=burger indirect=True scope=None
- collected 6 items
- test_fastfood.py
- SETUP S burger[big mac]
- test_fastfood.py::test_burgers[big mac] (fixtures used: burger)
- TEARDOWN S burger[big mac]
- SETUP S burger[whopper]
- test_fastfood.py::test_burgers[whopper] (fixtures used: burger)
- TEARDOWN S burger[whopper]
- SETUP S burger[small mac]
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[curlyfries-small mac] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- TEARDOWN S burger[small mac]
- SETUP S burger[whooper]
- SETUP F fries[curlyfries]
- test_fastfood.py::test_combo1[curlyfries-whooper] (fixtures used: burger, fries)
- TEARDOWN F fries[curlyfries]
- TEARDOWN S burger[whooper]
- SETUP S burger[small mac]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[regularfries-small mac] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN S burger[small mac]
- SETUP S burger[whooper]
- SETUP F fries[regularfries]
- test_fastfood.py::test_combo1[regularfries-whooper] (fixtures used: burger, fries)
- TEARDOWN F fries[regularfries]
- TEARDOWN S burger[whooper]
- We've effectively turned our session scoped fixture into a function
- scoped fixture (it's created and destroyed for each test item) even
- though it's corresponding `FixtureDef` still thinks it's session scope.
- ## Conclusion
- The ordering works correctly regardless of parametrization method.
- Parametrization APIs, however, are confusing:
- - `metafunc.parametrize` and `pytest.mark.parametrize` allow
- parametrization of non-function scoped fixtures at the function level,
- - the above behaviour is the default (changing the default would also
- be bad for the reason stated in the next item of this list)
- - on top of this, if the scope is set correctly (e.g. to `"session"`)
- via `scope` parameter but the parameter list is not identical between
- different functions, things get very confusing.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement