When PyTest executes a test function that also requests the fixture function, pyTest determines the order in which its fixtures should be executed.

There are three influencing factors:

  • scopeThat’s what the fixture functions do, for examplescope='class'.
  • dependencies, there may be fixtures that request other fixtures, so dependencies will have to be taken into account.
  • autouseIf theautouse=TrueThat fixture is called first in scope.

So things like the name of the fixture function or test function, where it is defined, the order in which it is defined, and the order in which the fixture is requested, have no effect on the order of execution other than coincidence.

For these coincidences, pyTest does its best to keep the order of each run the same, but there are always surprises. Therefore, if we want to control the order, the safest way is to rely on the above three points, and to understand the dependencies.

First, use a wider range of fixture functions to take precedence

Fixtures for a larger scope (such as sessions) are executed before fixtures for a smaller scope (such as functions or classes).

Code examples:

import pytest


@pytest.fixture(scope="session")
def order():
    return []


@pytest.fixture
def func(order):
    order.append("function")


@pytest.fixture(scope="class")
def cls(order):
    order.append("class")


@pytest.fixture(scope="module")
def mod(order):
    order.append("module")


@pytest.fixture(scope="package")
def pack(order):
    order.append("package")


@pytest.fixture(scope="session")
def sess(order):
    order.append("session")


class TestClass:
    def test_order(self, func, cls, mod, pack, sess, order):
        assert order == ["session", "package", "module", "class", "function"]
Copy the code

Running results:

Test_module1. Py. [100%] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 1 passed in 0.01 s = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = the Process finished with exit code 0Copy the code

Now that it’s running, the fixture functions run in the order in the list [“session”, “package”, “module”, “class”, “function”].

Fixtures in the same order are executed based on dependencies

When one fixture function invites another, the other one executes first.

For example, fixtureA requests FixtureB and needs the result returned by B. So B goes first, because A depends on B, and b has to go first, otherwise A can’t do anything.

In addition, even if A does not need the results returned by B, A can still control the order by requesting B, as long as A needs to ensure that they are executed after B.

1. Request dependencies are linear

Code examples:

import pytest


@pytest.fixture
def order():
    return []


@pytest.fixture
def a(order):
    order.append("a")


@pytest.fixture
def b(a, order):
    order.append("b")


@pytest.fixture
def c(a, b, order):
    order.append("c")


@pytest.fixture
def d(c, b, order):
    order.append("d")


@pytest.fixture
def e(d, b, order):
    order.append("e")


@pytest.fixture
def f(e, order):
    order.append("f")


@pytest.fixture
def g(f, c, order):
    order.append("g")


def test_order(g, order):
    assert order == ["a", "b", "c", "d", "e", "f", "g"]
Copy the code

The dependency diagram (left) and execution sequence diagram (right) of the above code are given officially.



No square, just from the test functiontest_orderAt first, layer by layer, the fixture dependency worked its way up.

At this point, it becomes clear that if you want to control the order of execution, you need to provide enough information for these request dependencies. In this way pyTests can find a clear linear chain of dependencies and ultimately give the test functions that call them a definite order of operations.

2. If the request dependence is not linear, operation execution will be affected

In addition, if there is ambiguity and multiple execution orders occur, PyTest can be executed in any of these orders.

Based on the above request dependency graph (left), assuming that D does not request C, then the dependency becomes as shown on the right:

As can be seen:

  • C is now requested by only one G.
  • G is asking for both C and F.

Because c now has no dependencies other than a g, PyTest does not know whether C should be executed before f, e, or after d. At this point, PyTest assumes that C can be executed anywhere between G and B. That is, C must be executed after B and before G.

If this happens, your expected testing behavior or test results may be affected. You can change the code so that D doesn’t request C, and I’ve added print to keep track of the order of fixtures. Run the code:

Py run order run a run B run D run E run F run C run G f demo\test_module1.py:51 (test_order) ['a', 'b', 'd...'f', 'c', ...]. ! = ['a', 'b', 'c...'e', 'f', ...]  Expected :['a', 'b', 'c...'e', 'f', ...]  Actual :['a', 'b', 'd...'f', 'c', ...]Copy the code

You’ll see that the test failed because the order of the fixture was changed, causing the order of elements added to the order list to change and not match the expected result, and the test failed. But you can see from the order in which the printed fixture runs that C does run after B and before G.

What does the official description mean? If you want to have precise control over the order in which you perform operations or test results, you need to give pyTest enough dependency to order them linearly.

A: Autouse fixtures

1. Autouse

If a fixture function with Autouse =True is requested, the Autouse’s fixture function will be executed before any other requested fixture.

Also, if fixture A is autouse and fixture B is not. If fixture A requests fixture B, fixture B will also become autouse’s fixture, but only for tests that request A. In fact, this is also easy to understand, since A is to be executed first, A requested B, indicating that A is dependent on B, so B naturally should be executed before A.

In the previous example, because D did not request C, the dependency relationship was blurred, which ultimately affected the execution result. But if C is autouse, then B and A automatically become autouse, because C depends on B and A. So, c, B, and A all execute before the other non-Autouse fixture functions.

Modify the code and add autouse to c:

Import pytest@pytest. fixture def order(): print("\n run order") return [] @pytest.fixture def a(order): Fixture def b(a, order) print(" run a") order.append("a") @pytest.fixture def b(a, order): Print (" run b") order.append("b") @pytest.fixture(autouse=True) def c(a, b, order): Print (" run c") order.append("c") @pytest.fixture def d(b, order): Fixture def e(d, b, order): print(" run d") order.append("d") @pytest.fixture def e(d, b, order): Print (" run e") order.append("e") @pytest.fixture def f(e, order): Print (" run f") order.append("f") @pytest.fixture def g(f, c, order): Print (" g "operation) order. Append (" g") def test_order (g, order) : assert order = = [" a ", "b", "c", "d", "e", "f", "g"]Copy the code

Running results:

Test_module1. Py running order run run run run run run b c d e f run g. [100%] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 1 passed in 0.01 s ============================== Process finished with exit code 0Copy the code

The order of execution is normal, from A to G.

Their dependency graph looks like this:

Because c becomes Autouse, it is above D in the diagram, and PyTest can linearize the execution order again. Also, C makes both B and A fixtures for Autouse.

2. Careful use of Autouse

Be careful when using Autouse, too. Even if a test function doesn’t request an Autouse fixture directly, the Autouse automatically executes as long as the test function is in the autouse’s scope. See a code example:

import pytest


@pytest.fixture(scope="class")
def order():
    return []


@pytest.fixture(scope="class", autouse=True)
def c1(order):
    order.append("c1")


@pytest.fixture(scope="class")
def c2(order):
    order.append("c2")


@pytest.fixture(scope="class")
def c3(order, c1):
    order.append("c3")


class TestClassWithC1Request:
    def test_order(self, order, c1, c3):
        assert order == ["c1", "c3"]


class TestClassWithoutC1Request:
    def test_order(self, order, c2):
        assert order == ["c1", "c2"]
Copy the code

Order == [“c1”, “c2”] order == [“c1”, “c2”] As you can see, although the class TestClassWithoutC1Request (official wrote TestClassWithC1Request should be wrong) no request c1, but c1 or run in this class.

However, just because an Autouse fixture requests a non-Autouse fixture doesn’t mean that the non-Autouse fixture is also a fixture function that can be applied to the context. This is just the scope of the Autouse fixture that applies to it. For example, look at the following code:

import pytest


@pytest.fixture
def order():
    return []


@pytest.fixture
def c1(order):
    order.append("c1")


@pytest.fixture
def c2(order):
    order.append("c2")


class TestClassWithAutouse:
    @pytest.fixture(autouse=True)
    def c3(self, order, c2):
        order.append("c3")

    def test_req(self, order, c1):
        assert order == ["c2", "c3", "c1"]

    def test_no_req(self, order):
        assert order == ["c2", "c3"]


class TestClassWithoutAutouse:
    def test_req(self, order, c1):
        assert order == ["c1"]

    def test_no_req(self, order):
        assert order == []

if __name__ == '__main__':
    pytest.main(['-s', 'test_module2.py'])
Copy the code

All runs are passable, so the dependency diagram looks like this.

In class TestClassWithAutouse:

  • test_reqandtest_no_reqThere are two test methods.
  • c3It was Autouse and asked for itc2, soc2Also known as autouse. Although two tests did not request itc2andc3But it has been carried out and is inc1Before execution.

In this case, C3 requests the return order, and in c3’s scope, order also acts as autouse. In TestClassWithoutAutouse, however, order is not autouse, so test_no_req in TestClassWithoutAutouse should succeed because order=[].