How can I loop through pytest tests multiple times and ensure fixtures run every iteration?

I want to repeat the same pytest tests N times to measure execution speed or calculate average timing for different parameters.

I tried using the pytest_runtestloop hook like this:

def pytest_runtestloop(session):
    repeat = int(session.config.option.repeat)
    assert isinstance(repeat, int), "Repeat must be an integer"
    for i in range(repeat):                      
        session.config.pluginmanager.getplugin("main").pytest_runtestloop(session)
    return True

However, the setup fixture with scope="class" only runs during the first iteration. For example:

class TestSomething:

    @classmethod
    @pytest.fixture(scope="class", autouse=True)
    def setup(cls):
        # setup function

    def test_something(self):
        # test function

Here, setup is called only the first cycle, but test_something runs both times if repeat=2.

What am I doing wrong, and is there a better approach to repeat tests while re-running fixtures each time?

I have previously experienced the same issue, and the easiest approach is to leverage pytest.mark.parametrize to repeat the test multiple times.

This way, pytest naturally handles the fixture setup/teardown for each iteration:

import pytest

@pytest.mark.parametrize("iteration", range(2))  # repeat 2 times
class TestSomething:

    @pytest.fixture(autouse=True)
    def setup(self):
        print("Setup runs every iteration")
        yield
        print("Teardown after iteration")

    def test_something(self, iteration):
        print(f"Running iteration {iteration}")
        assert True

Here, setup will run for each iteration because pytest treats each parametrized combination as a new test.

This is simple and doesn’t require messing with internal hooks.

Another way I’ve done it when I only need to measure performance is to loop inside the test and call the fixture manually:

import pytest

class TestSomething:

    @pytest.fixture(autouse=True)
    def setup(self):
        print("Setup executed")
        yield
        print("Teardown executed")

    def test_something(self):
        repeat = 2
        for i in range(repeat):
            # manually trigger setup/teardown using a context manager pattern
            with self.setup():
                print(f"Iteration {i}")
                assert True

It’s a bit more manual, but it ensures the fixture logic runs every time.

The downside is you don’t get separate test reports per iteration.

Honestly, for this use case, the community-standard solution is pytest-repeat.

Install it via:

pip install pytest-repeat

Then you can just write:

import pytest

@pytest.mark.repeat(2) # repeat 2 times class TestSomething:

@pytest.fixture(autouse=True)
def setup(self):
    print("Setup runs every iteration")
    yield
    print("Teardown after iteration")

def test_something(self):
    print("Running test_something")
    assert True

The plugin handles rerunning the test and rerunning fixtures correctly. You also get proper reporting for each iteration.

Super handy if you want to scale it to N repeats without hacking pytest internals.