Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question re: order of execution of test fixtures #2861

Closed
TheFriendlyCoder opened this issue Oct 23, 2017 · 2 comments
Closed

Question re: order of execution of test fixtures #2861

TheFriendlyCoder opened this issue Oct 23, 2017 · 2 comments
Labels
topic: fixtures anything involving fixtures directly or indirectly type: question general question, might be closed after 2 weeks of inactivity

Comments

@TheFriendlyCoder
Copy link

Good day.

This is probably more of a question than a bug, but I'm curious if there are any docs describing the order of execution of test fixtures in pytest?

Specifically, I have the following use case / problem: I have 3 test fixtures I'm trying to organize:

  1. (fixture1) Function scoped fixture with auto-use enabled. This one configures logging for each unit test so output for each test gets redirected to a different log file. I set the fixture to autouse because I want this log redirection to happen automatically for every unit test.
  2. (fixture2) Session scoped fixture with auto-use disabled. This one sets up some heavy weight configuration that should only be run once per test session to conserve resources. I set auto-use to false since I only want to do this setup for tests that require it.
  3. (fixture3) Function scoped fixture with auto-use disabled. This fixture depends on the previous one, taking parts of the session wide test configuration and adding some function-specific bits to it that each test will require. Thus, test functions should never need to explicitly mark the session scoped fixture, but instead link to this function scoped one which, in turn should automatically run the session scoped fixture as necessary.

Now, most of this use case is pretty easy to orchestrate but here's where my problem lies. For tests that use fixture3, I need the 3 fixtures to run in the following order: fixture2, fixture1, fixture3. I can't seem to find a solution that ensures this order of execution whilst using the auto-use flag for fixture1. In every case fixture1 always runs first. Can someone perhaps explain what I might be doing wrong or how to get the desired behavior?

Here's a snippet of code that illustrates what my current implementation looks like:

@pytest.fixture(scope='function', autouse=True)
def config_test_logger(request):
    # (fixture1) 
    # configure a per-test logger for every test

@pytest.fixture(scope='session')
def setup_env():
    # (fixture2) 
    # configure heavy weight test setup logic, once per session and only for tests that need it
    # test should never directly depend on this fixture, but rather inherit this one indirectly
    # by using the test_env fixture defined below

@pytest.fixture(scope='function')
def test_env(setup_env, request):
    # (fixture3)
    # make sure our heavy weight session setup is done first, then customize the env with
    # test specific bits. Unit tests should only need to depend on this fixture when they need
    # to interact with the heave weight session stuff

The closest solution I found to get the desired behavior is to disable the autouse flag on fixture1 (aka: config_test_logger) and add an explicit dependency on this fixtures to fixture3 (aka: test_env) like this:

@pytest.fixture(scope='function')
def config_test_logger(request):
    # (fixture1) 
    # configure a per-test logger, now must be explicitly included by each test

@pytest.fixture(scope='session')
def setup_env():
    # (fixture2) 
    # configure heavy weight test setup logic, once per session and only for tests that need it

@pytest.fixture(scope='function')
def test_env(setup_env, config_test_logger, request):
    # (fixture3)
    # make sure our heavy weight session setup is done first, then customize the env with
    # test specific bits. Unit tests should only need to depend on this fixture when they need
    # to interact with the heave weight session stuff

This slight variation has the benefit of ensuring the order of execution of the fixtures is correct (ie: fixture2, fixture1, fixture3) but has the down side of requiring every other unit test in the suite to explicitly mark a dependency on fixture1, which is overly verbose and fragile at best.

I've also tried several variations of this including explicitly listing config_test_logger as a dependent fixture in test_env and leaving the autouse flag enabled on the former, renaming the fixture methods so they follow an alphabetical ordering (ie: a_setup_env, b_test_env, c_config_test_logger) just in case the ordering was alphabetic, and several other things but I can't seem to find a solution that gets me exactly what I'm looking for.

Any help anyone has would be greatly appreciated.

@nicoddemus
Copy link
Member

Hi @TheFriendlyCoder,

Currently all autouse fixtures in a node are executed first. You can see this here:

pytest/_pytest/fixtures.py

Lines 1004 to 1032 in 531e0dc

def getfixtureclosure(self, fixturenames, parentnode):
# collect the closure of all fixtures , starting with the given
# fixturenames as the initial set. As we have to visit all
# factory definitions anyway, we also return a arg2fixturedefs
# mapping so that the caller can reuse it and does not have
# to re-discover fixturedefs again for each fixturename
# (discovering matching fixtures for a given name/node is expensive)
parentid = parentnode.nodeid
fixturenames_closure = self._getautousenames(parentid)
def merge(otherlist):
for arg in otherlist:
if arg not in fixturenames_closure:
fixturenames_closure.append(arg)
merge(fixturenames)
arg2fixturedefs = {}
lastlen = -1
while lastlen != len(fixturenames_closure):
lastlen = len(fixturenames_closure)
for argname in fixturenames_closure:
if argname in arg2fixturedefs:
continue
fixturedefs = self.getfixturedefs(argname, parentid)
if fixturedefs:
arg2fixturedefs[argname] = fixturedefs
merge(fixturedefs[-1].argnames)
return fixturenames_closure, arg2fixturedefs

There's no way to override this behavior currently, I'm afraid.

@nicoddemus nicoddemus added topic: fixtures anything involving fixtures directly or indirectly type: question general question, might be closed after 2 weeks of inactivity labels Oct 23, 2017
@TheFriendlyCoder
Copy link
Author

Thanks for confirming.

Luckily I found a way to break the dependencies between my auto-used fixtures and the non-auto-used ones, so as to avoid the problem I was having. However, it would be nice if there were some way to reprioritize the execution order of the fixtures somehow. Given that I no longer have an applicable use case, I'll leave that up to your project maintainers to decide whether that's something worth investigating or not.

Thanks again for your help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: fixtures anything involving fixtures directly or indirectly type: question general question, might be closed after 2 weeks of inactivity
Projects
None yet
Development

No branches or pull requests

2 participants