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.