Using pytest-dependency

The plugin defines a new marker pytest.mark.dependency().

Basic usage

Consider the following example test module:

import pytest

@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
    assert False

@pytest.mark.dependency()
def test_b():
    pass

@pytest.mark.dependency(depends=["test_a"])
def test_c():
    pass

@pytest.mark.dependency(depends=["test_b"])
def test_d():
    pass

@pytest.mark.dependency(depends=["test_b", "test_c"])
def test_e():
    pass

All the tests are decorated with pytest.mark.dependency(). This will cause the test results to be registered internally and thus other tests may depend on them. The list of dependencies of a test may be set in the optional depends argument to the marker. Running this test, we will get the following result:

$ pytest -rsx basic.py 
============================= test session starts ==============================
platform linux -- Python 3.8.1, pytest-5.3.4, py-1.8.1, pluggy-0.13.1
rootdir: /home/user/tests
plugins: dependency-0.4.0
collected 5 items                                                              

basic.py x.s.s                                                           [100%]

=========================== short test summary info ============================
SKIPPED [1] /usr/lib/python3.8/site-packages/pytest_dependency.py:87: test_c depends on test_a
SKIPPED [1] /usr/lib/python3.8/site-packages/pytest_dependency.py:87: test_e depends on test_c
XFAIL basic.py::test_a
  deliberate fail
=================== 2 passed, 2 skipped, 1 xfailed in 0.06s ====================

The first test has deliberately been set to fail to illustrate the effect. We will get the following results:

test_a

deliberately fails.

test_b

succeeds.

test_c

will be skipped because it depends on test_a.

test_d

depends on test_b which did succeed. It will be run and succeed as well.

test_e

depends on test_b and test_c. test_b did succeed, but test_c has been skipped. So this one will also be skipped.

Naming tests

Tests are referenced by their name in the depends argument. The default for this name is the node id defined by pytest, that is the name of the test function, extended by the parameters if applicable, see Section Names for details. In some cases, it’s not easy to predict the names of the node ids. For this reason, the name of the tests can be overridden by an explicit name argument to the marker. The names must be unique. The following example works exactly as the last one, only the test names are explicitly set:

import pytest

@pytest.mark.dependency(name="a")
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
    assert False

@pytest.mark.dependency(name="b")
def test_b():
    pass

@pytest.mark.dependency(name="c", depends=["a"])
def test_c():
    pass

@pytest.mark.dependency(name="d", depends=["b"])
def test_d():
    pass

@pytest.mark.dependency(name="e", depends=["b", "c"])
def test_e():
    pass

Using test classes

Tests may be grouped in classes in pytest. Marking the dependencies of methods in test classes works the same way as for simple test functions. In the following example we define two test classes. Each works in the same manner as the previous examples respectively:

import pytest


class TestClass(object):

    @pytest.mark.dependency()
    @pytest.mark.xfail(reason="deliberate fail")
    def test_a(self):
        assert False

    @pytest.mark.dependency()
    def test_b(self):
        pass

    @pytest.mark.dependency(depends=["TestClass::test_a"])
    def test_c(self):
        pass

    @pytest.mark.dependency(depends=["TestClass::test_b"])
    def test_d(self):
        pass

    @pytest.mark.dependency(depends=["TestClass::test_b", "TestClass::test_c"])
    def test_e(self):
        pass


class TestClassNamed(object):

    @pytest.mark.dependency(name="a")
    @pytest.mark.xfail(reason="deliberate fail")
    def test_a(self):
        assert False

    @pytest.mark.dependency(name="b")
    def test_b(self):
        pass

    @pytest.mark.dependency(name="c", depends=["a"])
    def test_c(self):
        pass

    @pytest.mark.dependency(name="d", depends=["b"])
    def test_d(self):
        pass

    @pytest.mark.dependency(name="e", depends=["b", "c"])
    def test_e(self):
        pass

In TestClass the default names for the tests are used, which is build from the name of the class and the respective method in this case, while in TestClassNamed these names are overridden by an explicit name argument to the pytest.mark.dependency() marker.

Changed in version 0.3: The name of the class is prepended to the method name to form the default name for the test.

Applying the dependency marker to a class as a whole

The pytest.mark.dependency() marker may also be applied to a test class as a whole. This has the same effect as applying that marker with the same arguments to each method of the class individually. Consider:

import pytest


@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_f():
    assert False


@pytest.mark.dependency(depends=["test_f"])
class TestClass(object):

    def test_a(self):
        pass

    @pytest.mark.dependency()
    def test_b(self):
        pass

    def test_c(self):
        pass

The tests TestClass::test_a and TestClass::test_c will be skipped, because they depend on test_f. But TestClass::test_b will be run, because it is individually marked. The marker on the test method overrides the marker on the class and thus effectively clears the dependency list for TestClass::test_b.

Parametrized tests

In the same way as the pytest.mark.skip() and pytest.mark.xfail() markers, the pytest.mark.dependency() marker may be applied to individual test instances in the case of parametrized tests. Consider the following example:

import pytest

@pytest.mark.parametrize("x,y", [
    pytest.param(0, 0, marks=pytest.mark.dependency(name="a1")),
    pytest.param(0, 1, marks=[pytest.mark.dependency(name="a2"),
                              pytest.mark.xfail]),
    pytest.param(1, 0, marks=pytest.mark.dependency(name="a3")),
    pytest.param(1, 1, marks=pytest.mark.dependency(name="a4"))
])
def test_a(x,y):
    assert y <= x

@pytest.mark.parametrize("u,v", [
    pytest.param(1, 2, marks=pytest.mark.dependency(name="b1", 
                                                    depends=["a1", "a2"])),
    pytest.param(1, 3, marks=pytest.mark.dependency(name="b2", 
                                                    depends=["a1", "a3"])),
    pytest.param(1, 4, marks=pytest.mark.dependency(name="b3", 
                                                    depends=["a1", "a4"])),
    pytest.param(2, 3, marks=pytest.mark.dependency(name="b4", 
                                                    depends=["a2", "a3"])),
    pytest.param(2, 4, marks=pytest.mark.dependency(name="b5", 
                                                    depends=["a2", "a4"])),
    pytest.param(3, 4, marks=pytest.mark.dependency(name="b6", 
                                                    depends=["a3", "a4"]))
])
def test_b(u,v):
    pass

@pytest.mark.parametrize("w", [
    pytest.param(1, marks=pytest.mark.dependency(name="c1", 
                                                 depends=["b1", "b2", "b6"])),
    pytest.param(2, marks=pytest.mark.dependency(name="c2", 
                                                 depends=["b2", "b3", "b6"])),
    pytest.param(3, marks=pytest.mark.dependency(name="c3", 
                                                 depends=["b2", "b4", "b6"]))
])
def test_c(w):
    pass

The test instance test_a[0-1], named a2 in the pytest.mark.dependency() marker, is going to fail. As a result, the dependent tests b1, b4, b5, and in turn c1 and c3 will be skipped.

Marking dependencies at runtime

Sometimes, dependencies of test instances are too complicated to be formulated explicitly beforehand using the pytest.mark.dependency() marker. It may be easier to compile the list of dependencies of a test at run time. In such cases, the function pytest_dependency.depends() comes handy. Consider the following example:

import pytest
from pytest_dependency import depends

@pytest.mark.dependency()
def test_a():
    pass

@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_b():
    assert False

@pytest.mark.dependency()
def test_c(request):
    depends(request, ["test_b"])
    pass

@pytest.mark.dependency()
def test_d(request):
    depends(request, ["test_a", "test_c"])
    pass

Tests test_c and test_d set their dependencies at runtime calling pytest_dependency.depends(). The first argument is the value of the request pytest fixture, the second argument is the list of dependencies. It has the same effect as passing this list as the depends argument to the pytest.mark.dependency() marker.

The present example is certainly somewhat artificial, as the use of the pytest_dependency.depends() function would not be needed in such a simple case. For a more involved example that can not as easily be formulated with the static the depends argument, see Grouping tests using fixtures.