Skip to content

Instantly share code, notes, and snippets.

@jwineinger
Created March 23, 2018 18:21
Show Gist options
  • Save jwineinger/e41473cd5500554a189d8c97ac28f883 to your computer and use it in GitHub Desktop.
Save jwineinger/e41473cd5500554a189d8c97ac28f883 to your computer and use it in GitHub Desktop.
reorder pytest tests based on dependencies
def pytest_collection_modifyitems(session, config, items):
"""Reorder the execution of tests based on the dependency marks on tests.
To setup a test dependency, decorate the test function with the "depends"
mark and give a list of dependencies via the "on" keyword arg to the
decorator. Dependencies can be specified by name only when in the same
file as the test being decorated, or by pytest node path for tests in
other files/classes.
@pytest.mark.depends(on=["test_in_same_file",
"test_other.py::test_in_other_file",
"test_other.py::TestClass::test_method"])
def test_with_dependencies():
pass
Tests without dependencies will run in arbitrary order.
"""
def _nodeid_cleaner(nodeid):
"""Clean node ids for class-based tests so they're easier to refer to."""
return nodeid.replace('::()::', '::')
dag = nx.DiGraph()
nodeid_to_item_map = {_nodeid_cleaner(item.nodeid): item for item in items}
list(map(lambda x: dag.add_node(_nodeid_cleaner(x.nodeid)), items))
for item in items:
marker = item.get_marker("depends")
if marker is not None:
depends = marker.kwargs.get('on', [])
for dependency in depends:
if '::' not in dependency:
base_item_nodeid = _nodeid_cleaner(item.nodeid).rsplit('::', 1)[0]
dependency = f"{base_item_nodeid}::{dependency}"
dag.add_edge(dependency, _nodeid_cleaner(item.nodeid))
reordered = [nodeid_to_item_map[node] for node in nx.topological_sort(dag)]
items[:] = reordered
@jwineinger
Copy link
Author

Could use pytestmark = [pytest.mark.depends(on=["other_file.py::last_test"]) at the top of a test file to make sure every test there runs after the last test in other_file.py

@jwineinger
Copy link
Author

Put conftest.py in your top-level test directory.

@ftesser
Copy link

ftesser commented Apr 23, 2019

@jwineinger thanks for sharing your code.
If you are interested, I have forked your gist (https://gist.github.com/ftesser/4919ac83ad9e912e72e8a532d7ef6504) and make a little change to achieve the compatibility with pytest > 4.0:
to use get_closest_marker() instead of get_marker() as stated in https://stackoverflow.com/questions/54254337/pytest-attributeerror-function-object-has-no-attribute-get-marker.

@jwineinger
Copy link
Author

thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment