funcarg_compare.rst
Overview
This file is a detailed explanatory document focused on the evolution of pytest's *funcarg* mechanism into the modern *fixture* system introduced in pytest version 2.3. It serves as a historical and technical guide for pytest users and plugin authors who are familiar with older pytest versions and want to understand the reasons and benefits behind the fixture redesign.
The document contrasts the limitations of the old `pytest_funcarg__` factory style with the new `@pytest.fixture` decorator approach, elaborates on scoping and parametrization improvements, and discusses how these changes address common testing challenges. It also covers autouse fixtures and fixture discovery enhancements introduced in pytest-2.3, along with compatibility notes to assist migration from older pytest versions.
This file is primarily educational and is intended to be read within the pytest documentation to provide context and rationale for the fixture system design decisions.
Detailed Explanation
Historical Context and Purpose
Prior to pytest-2.3, pytest used a *funcarg* mechanism to provide test function arguments (resources needed during testing). This was done using special factory functions named with a prefix `pytest_funcarg__`. These factories created resources on demand but had limitations in scoping, parametrization, and reusability.
The file explains these shortcomings and demonstrates how pytest-2.3 introduced *fixtures* with the `@pytest.fixture` decorator to replace funcargs, simplifying resource management and enabling powerful features such as scoped resource lifetimes, direct parametrization, and autouse fixtures.
Key Sections and Concepts
1. Shortcomings of the previous pytest_funcarg__ mechanism
Resource caching and scoping was handled manually via
request.cached_setup().Parametrization was indirect and required additional hooks (
pytest_generate_tests).Global state management was complicated when multiple parametrized session resources were used.
Integration with xUnit setup methods was not possible.
Non-parametrized fixtures could not depend on parametrized funcargs unless explicitly stated in test signatures.
Example of old style funcarg:
# content of conftest.py
class Database:
def __init__(self):
print("database instance created")
def destroy(self):
print("database instance destroyed")
def pytest_funcarg__db(request):
return request.cached_setup(
setup=Database, teardown=lambda db: db.destroy, scope="session"
)
2. Direct scoping of fixture/funcarg factories
The new `@pytest.fixture` decorator allows declaring the scope of a fixture directly, making caching and setup/teardown automatic and transparent.
Example of scoped fixture:
@pytest.fixture(scope="session")
def db(request):
db = Database()
request.addfinalizer(db.destroy) # teardown at session end
return db
Parameters:
scope: defines lifetime of the resource, e.g.,"session","module","function".
Return Value: the created resource object (e.g.,
Databaseinstance).Usage: any test function or fixture that includes
dbas an argument will receive this resource.
3. Direct parametrization of funcarg resource factories
Fixtures can be parametrized using the `params` argument in the decorator. Each parameter value results in a separate fixture instance and test invocation.
Example of parametrized fixture:
@pytest.fixture(params=["mysql", "pg"])
def db(request):
if request.param == "mysql":
db = MySQL()
elif request.param == "pg":
db = PG()
request.addfinalizer(db.destroy)
return db
request.paramcontains the current parameter value.Tests depending on
dbrun once per parameter.Parametrized fixtures can also be scoped.
4. No pytest_funcarg__ prefix when using @fixture decorator
The fixture function name defines the argument name.
The legacy
pytest_funcarg__nameprefix style is still supported but discouraged.The decorator approach is more feature-rich and simpler to use.
5. Solving per-session setup / autouse fixtures
Old hooks like
pytest_configureandpytest_sessionstartfor global setup had drawbacks:Inefficient in distributed testing.
Executed during collection even when tests are not run.
Not reliably called in certain directory structures.
pytest-2.3 introduced autouse fixtures integrated with the fixture system to provide automatic setup without explicit test arguments.
6. Fixture discovery at collection time
Fixtures are discovered during test collection.
This improves efficiency and allows tools to report fixture setup information during
pytest --collect-only.
7. Conclusion and compatibility notes
The document summarizes the differences between old funcargs and new fixtures.
Emphasizes the benefits of the new fixture system:
Clear scoping.
Easier parametrization.
Better integration with pytest internals.
Encourages migration to
@pytest.fixturedecorator style.
Usage Examples
Using a session-scoped fixture:
@pytest.fixture(scope="session")
def db(request):
db = Database()
request.addfinalizer(db.destroy)
return db
def test_query(db):
assert db.query("SELECT 1") == 1
Parametrized fixture example:
@pytest.fixture(params=["sqlite", "postgres"])
def db(request):
if request.param == "sqlite":
db = SQLiteDB()
else:
db = PostgresDB()
request.addfinalizer(db.close)
return db
def test_db_connection(db):
assert db.is_connected()
Implementation Details
requestobject: Passed to fixture functions, provides access to:addfinalizer(func): registers teardown logic associated with the fixture's scope.param: current parameter value when fixture is parametrized.cached_setup(): legacy caching mechanism.
Fixture scopes:
"function"(default),"class","module","package","session".Finalizers: Registered teardown functions run after the fixture's scope ends.
Parametrization: Implemented internally to generate multiple test invocations per parameter set.
Autouse fixtures: Fixtures that are automatically used without explicit declaration in test signatures.
Interactions with Other System Components
This document relates closely to pytest’s core test collection and execution phases.
The fixture system integrates with:
Test functions: fixtures provide arguments.
Test classes and modules: fixtures can be scoped accordingly.
Parametrization hooks and test generation.
Plugin system: plugins can provide fixtures.
Test reporting: parametrized fixtures influence test names and reports.
The file itself is a documentation source and not executable code, but it references implementation concepts and APIs used by pytest core and plugin authors.
Mermaid Diagram
flowchart TD
A[Old funcarg mechanism] -->|Uses| B[pytest_funcarg__NAME factories]
B -->|Manual caching| C[request.cached_setup()]
C -->|Complex scoping and teardown| D[Limitations]
E[pytest-2.3 Fixture system]
E -->|@pytest.fixture decorator| F[Fixture function]
F -->|Defines scope and params| G[Automatic resource caching]
G -->|Uses| H[request.addfinalizer()]
F -->|Can be parametrized| I[request.param]
I -->|Generates multiple test runs| J[Parametrized test invocations]
E -->|Supports| K[autouse fixtures]
K -->|Automatic setup/teardown| L[Improved global resource management]
D -->|Addressed by| E
Summary
This documentation file is an in-depth, historical, and technical explanation of the transition from pytest’s older funcarg system to the modern fixture system introduced in pytest-2.3. It explains key concepts such as scoping, parametrization, autouse fixtures, and fixture discovery, illustrating how these innovations resolve earlier limitations and improve test resource management. The file is intended for pytest users with some experience who want to understand the rationale behind fixture design and migrate legacy code accordingly.
**End of funcarg_compare.rst documentation**