python-3.xpytestpytest-order

Pytest order test A, then test B and then test A again


Is it possible to order tests the way, that test A will go first, then test B go second and then test A will go again as third? I am using pytest-order lib.

Tech stack: Python 3.8.5, pytest 6.1.0, pytest-order 1.0.1

Here is the code used:

import logging
import pytest


@pytest.mark.order(2)
def test_a():
    logging.info('test_a')
    pass


@pytest.mark.order(1)
@pytest.mark.order(3)
def test_b():
    logging.info('test_b')
    pass

But test B is not executed third time. Only once as first one due to the two order marks.

Output:

=================== test session starts ==================

collecting ... collected 2 items

test.py::test_a PASSED [ 50%]

test.py::test_b PASSED [100%]

=================== 2 passed in 0.07s ===================


Solution

  • Actually pytest-order does not allow to add two order marks. However I came up with some solution for you.

    You can resolve this syntax using pytest_generate_tests pytest hook. Just add it to your conftest.py file.

    Example below will read all pytest.mark.order marks and make parametrized test out of it (if more than 1 order mark was provided). It adds parameter called order which stores arguments specified in pytest.mark.order.

    conftest.py

    def _get_mark_description(mark):
        if mark.kwargs:
            return ", ".join([f"{k}={v}" for k, v in mark.kwargs.items()])
        elif mark.args:
            return f"index={mark.args[0]}"
        return mark
    
    
    def pytest_generate_tests(metafunc):
        """
        Handle multiple pytest.mark.order decorators.
    
        Make parametrized tests with corresponding order marks.
        """
        if getattr(metafunc, "function", False):
            if getattr(metafunc.function, "pytestmark", False):
                # Get list of order marks
                marks = metafunc.function.pytestmark
                order_marks = [
                    mark for mark in marks if mark.name == "order"
                ]
                if len(order_marks) > 1:
                    # Remove all order marks
                    metafunc.function.pytestmark = [
                        mark for mark in marks if mark.name != "order"
                    ]
                    # Prepare arguments for parametrization with order marks
                    args = [
                        pytest.param(_get_mark_description(mark), marks=[mark])
                        for mark in order_marks
                    ]
                    if "order" not in metafunc.fixturenames:
                        metafunc.fixturenames.append("order")
                    metafunc.parametrize('order', args)
    

    test_order.py

    import pytest
    
    
    @pytest.mark.order(6)
    @pytest.mark.order(4)
    def test_4_and_6():
        pass
    
    
    @pytest.mark.order(5)
    @pytest.mark.order(3)
    @pytest.mark.order(1)
    def test_1_3_and_5():
        pass
    
    
    @pytest.mark.order(2)
    def test_2():
        pass
    

    pytest test_order.py -v Output

    collecting ... collected 6 items
    
    test_order.py::test_1_3_and_5[index=1] 
    test_order.py::test_2 
    test_order.py::test_1_3_and_5[index=3] 
    test_order.py::test_4_and_6[index=4] 
    test_order.py::test_1_3_and_5[index=5] 
    test_order.py::test_4_and_6[index=6] 
    

    As you can see, all tests ran in defined order.

    UPD

    I have updated hook to make it compatible with other features of pytest-order. Also I have created PR in pytest-order GitHub repo.