4

I am writing tests with pytest in pycharm. The tests are divided into various classes.
I would like to specify certain classes that have to run before other classes.
I have seen various questions on stackoverflow (such as specifying pytest tests to run from a file and how to run a method before all other tests).
These and various others questions wanted to choose specific functions to run in order. This can be done, I understand, using fixtures or with pytest ordering.
I don't care which functions from each class run first. All I care about is that the classes run in the order I specify. Is this possible?

user613
  • 175
  • 2
  • 10

2 Answers2

6

Approach

You can use the pytest_collection_modifyitems hook to modify the order of collected tests (items) in place. This has the additional benefit of not having to install any third party libraries.

With some custom logic, this allows to sort by class.

Full example

Say we have three test classes:

  1. TestExtract
  2. TestTransform
  3. TestLoad

Say also that, by default, the test order of execution would be alphabetical, i.e.:

TestExtract -> TestLoad -> TestTransform

which does not work for us due to test class interdependencies.

We can add pytest_collection_modifyitems to conftest.py as follows to enforce our desired execution order:

# conftest.py
def pytest_collection_modifyitems(items):
    """Modifies test items in place to ensure test classes run in a given order."""
    CLASS_ORDER = ["TestExtract", "TestTransform", "TestLoad"]
    class_mapping = {item: item.cls.__name__ for item in items}

    sorted_items = items.copy()
    # Iteratively move tests of each class to the end of the test queue
    for class_ in CLASS_ORDER:
        sorted_items = [it for it in sorted_items if class_mapping[it] != class_] + [
            it for it in sorted_items if class_mapping[it] == class_
        ]
    items[:] = sorted_items

Some comments on the implementation details:

  • Test classes can live in different modules
  • CLASS_ORDER does not have to be exhaustive. You can reorder just those classes on which to want to enforce an order (but note: if reordered, any non-reordered class will execute before any reordered class)
  • The test order within the classes is kept unchanged
  • It is assumed that test classes have unique names
  • items must be modified in place, hence the final items[:] assignment
swimmer
  • 1,971
  • 2
  • 17
  • 28
  • 1
    This looks like just what I need! And, you even answered the follow up question I would've had, by saying that CLASS_ORDER does not have to be exhaustive. Thanks for this helpful answer! – user613 Jan 19 '22 at 09:30
  • I'm having a problem with this solution. Any time I try to run some of the tests, and don't include every single class that I'm sorting I get an error message. Any idea how to fix this besides for hiding the classes I'm not using from the list? – user613 Jan 30 '22 at 10:07
  • 1
    Answer edited to change the sorting algorithm to one that shouldn't have that issue (plus is more readable) – swimmer Jan 30 '22 at 12:49
  • Thanks for the update. Works great! From what I understand, your new algorithm takes the class name, if it is in the list, and otherwise nothing. Is this correct? – user613 Jan 30 '22 at 17:39
  • 1
    Class by class according to the order defined in `CLASS_ORDER`, all tests belonging to a test class are moved to the end of the test queue (i.e. "execute last"). The order in `CLASS_ORDER` is enforced as a result. – swimmer Jan 30 '22 at 17:59
  • Does this mean that the tests inside a class will run in a specific order, too? – user613 Jan 31 '22 at 07:36
  • Yes, as indicated in the answer *The test order within the classes is kept unchanged* (as we move tests one by one to the end of the test queue in the order we find them) – swimmer Jan 31 '22 at 08:56
2

@swimmer answer is great. I've modified it slightly to work with test organized into functions instead of classes.

def pytest_collection_modifyitems(session, config, items):
    """Modifies test items in place to ensure test functions run in a given order"""
    function_order = ["test_one", "test_two"]
    # OR
    # function_order = ["test_one[1]", "test_two[2]"]  
    function_mapping = {item: item.name.split("[")[0] 
                        if "]" not in function_order[0] 
                        else item.name
                        for item in items}

    sorted_items = items.copy()
    for func_ in function_order:
        sorted_items = [it for it in sorted_items if function_mapping[it] != func_] + [it for it in sorted_items if function_mapping[it] == func_]
    items[:] = sorted_items