TIL: tests 20x faster - pytest-plone function-style tests re-run the layer setUp on every test

Spent a morning chasing a slow CI on a Plone 6.1 project and tracked it down to something that probably bites more people, so here it is.

Symptom: test suite crawling. pytest --durations=0 showed the costliest entries all in the setup phase, ~2.7s each, not in call. It is layer thrashing, not slow tests.

Root cause: with plain function-style tests (def test_x(portal): ..., using the pytest-plone fixtures) the full layer setUp - including applyProfile - runs once per test. zope.pytestlayer's keep-optimization only kicks in for items that carry a .layer attribute, i.e. unittest.TestCase classes the old zope.testrunner way. Function tests have no .layer, so the class-scoped layer fixture is torn down and set up again for every single test. Counted it: 15 tests -> 15 full layer setups.

Fix is one fixture in conftest.py:

@pytest.fixture(autouse=True, scope="session")
def _keep_layers_for_session(integration_session):
    """Keep the expensive layer set up for the whole session."""

integration_session is the session-scoped fixture fixtures_factory already generates (<prefix>_session). Requesting it once parks the layer in keep_for_whole_session so it survives the run. Per-test isolation is untouched — IntegrationTesting still rolls back the transaction per test.

Result on my suite: ~275s -> ~14s, 253 tests, ~20x

Question for the maintainers / the room: shouldn't pytest-plone keep the layers session-wide by default? With everyone moving from TestCase to function-style tests this is an easy, silent performance cliff to fall off. Happy to open a PR if there's agreement on the approach.

6 Likes