pytest-dev / pytest-dev/pytest
How should fixtures scopes work for nested packages and classes?
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 14.5k
- Forks
- 3.4k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 35
Description
We have two collection nodes (and fixture scopes), Package and Class, which may be nested. I'll use classes for example because it's easier to discuss (single file) but it's the same for packages.
Consider the following:
import pytest
@pytest.fixture(scope="class")
def fix(request):
print("SETUP", request.node)
yield
print("TEARDOWN", request.node)
class TestTop:
def test_top(self, fix):
print("top")
class TestNested1:
def test_nested1(self, fix):
print("nested1")
class TestNested2:
def test_nested2(self, fix):
print("nested2")
The collection tree is:
<Module x.py>
<Class TestTop>
<Function test_top>
<Class TestNested1>
<Function test_nested1>
<Class TestNested2>
<Function test_nested2>
What should be printed?
(I'm adding some indentation to the outputs to make them clearer).
Option 1 - Current behavior
The current behavior is the following.
SETUP <Class TestTop>
top
nested1
TEARDOWN <Class TestTop>
SETUP <Class TestNested2>
nested2
TEARDOWN <Class TestNested2>
Technical details why it happens
Each fixture definition is pytest becomes a FixtureDef instance. In the example we have one FixtureDef, for fix.
In the current implementation, the FixtureDef itself holds a fixture's value (cached_value). When a fixture value is requested (SubRequest.getfixturevalue()), if the FixtureDef's cached_value is not already set, it calculates it by running the fixture function. Then it schedules a finalizer on the fixture request's node (request.node) to clear the cached_value.
In the example:
test_toprequestsfixfor the first time, which runs the fixture and schedules to clearfix'scached_resultwhenTestTopis torn down.test_nested1requestsfix. At this point thefixFixtureDefhascached_valueset (sinceTestTop's finalizer hasn't run yet), so it just uses it without rerunning the fixture. Then it schedules a finalizer to clearfixwhenTestNested1is torn down.TestNested1is torn down which clearsfix.test_nested2requestsfix. Thecached_valueis cleared so it reruns the fixture.TestNested2is torn down, clearsfix.TestTopis torn down, wants to clearcached_value, but it is already cleared, goes 🤷 and continues.
Option 2 - Nested scope
Nested classes/packages get their own scope, nested within the parent class/package scope.
SETUP <Class TestTop>
top
SETUP <Class TestNested1>
nested1
TEARDOWN <Class TestNested1>
SETUP <Class TestNested2>
nested2
TEARDOWN <Class TestNested2>
TEARDOWN <Class TestTop>
Option 3 - Independent scope
Nested classes/packages get their own scope, independent of the parent class/package.
SETUP <Class TestTop>
top
TEARDOWN <Class TestTop>
SETUP <Class TestNested1>
nested1
TEARDOWN <Class TestNested1>
SETUP <Class TestNested2>
nested2
TEARDOWN <Class TestNested2>
Option 4 - Shared scope
Nested classes do not have a scope of their own; same scope as the top class/package.
SETUP <Class TestTop>
top
nested1
nested2
TEARDOWN <Class TestNested2>
My opinion
Option 1 seems broken to me, I don't think there's any case to be made for it.
Option 2 is my preference, I think it's the most coherent with the collection tree, lexical/filesystem layout and is pretty intuitive, for both packages and classes.
Option 3 I think is not intuitive, i.e. I think most would not expect fixtures to behave this way. It imposes some restrictions on tests ordering between the class and nested. Note that currently due to #7777, Packages work like this in practice.
Option 4 would be my second choice, it does make sense logically (that what if there is no test_top?), but I think it's less intuitive than Option 2.
I can write more but don't want to make this too long.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the fixture-scope behavior described through FixtureDef, cached_value, and SubRequest.getfixturevalue(), and compare it with the collection tree and the package behavior referenced in #7777. Determine which of the four scope options should be adopted, then validate the decision against nested classes and packages; done means the behavior is agreed and consistently implemented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100