Support fixtures as describe block arguments#54
Merged
Conversation
Proof of concept for injecting pytest fixtures declared as parameters of describe blocks, shared by all tests nested in the block: - At collection, the describe function is called with named placeholder objects, and the closure cells holding them are recorded. - A pytest_generate_tests hook adds the declared names to the fixture closure (names_closure + initialnames + arg2fixturedefs), so pytest itself resolves the fixtures and parametrized fixtures multiply tests. - Hook wrappers around test setup/teardown write the resolved fixture values into the closure cells and restore the placeholders afterwards. Unlike the earlier proof of concept in PR #39, test functions are not wrapped, so @pytest.mark.parametrize and signature introspection keep working. Verified with tox against pytest 7.0-9.0 and latest on CPython and against pytest 8.4 on PyPy; ruff and strict mypy pass. Known spike limitations: shared behaviors with arguments, misuse of placeholder values inside the describe body (no guard yet), transitive parametrized dependencies of injected fixtures, docs and full coverage.
Fixtures declared as parameters of a describe block (or of a shared
behavior) are now injected into all tests nested in the block:
def describe_create_book(user):
def with_valid_book(valid_book):
... # may use both the user and valid_book fixtures
Implementation:
- At collection, the describe function is called with named placeholder
objects instead of failing, and the closure cells holding the
placeholders are recorded on the DescribeBlock. The placeholders
raise a descriptive error when used in the describe body itself.
- A pytest_generate_tests hook adds the declared names and their
transitive dependencies to the fixture closure of each test in the
block, so pytest resolves the fixtures itself and parametrized
fixtures multiply the tests as usual.
- An autouse fixture, created lazily for each block with arguments,
resolves the fixture values, writes them into the closure cells
before other function-scoped fixtures of the block run, and restores
the placeholders on teardown. This also allows fixtures defined in
the block to use the describe arguments.
- Parameter handling mirrors pytest's fixture name detection
(mandatory positional-or-keyword and keyword-only parameters).
Unlike the earlier proof of concept in PR #39, test functions are not
wrapped, so @pytest.mark.parametrize and signature introspection keep
working. Thanks to @ROpdebee for the original investigation.
Closes #38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #38, supersedes #39. Thanks to @ROpdebee for the original investigation.
Fixtures declared as parameters of a describe block (or a shared behavior) are injected into all tests nested in the block. Unlike the earlier PoC, test functions are not wrapped: the declared names are added to each test's fixture closure in a
pytest_generate_testshook (so pytest resolves them itself and parametrized fixtures multiply tests,@pytest.mark.parametrizekeeps working), and an autouse fixture writes the resolved values into the closure cells before each test, restoring placeholders on teardown. Using an argument in the describe body itself raises a descriptive collection-time error.