Skip to content

Commit 41a7936

Browse files
authored
Added rules for pytest fixtures: (#17)
* Added rules to pytest fixtures: - rule for deprecated fixture scope as positional argument - rule for useless pytest.mark.usefixtures for fixtures - added tests for these checks * added to readme * Fixed comment in PR: - used check for all `@pytest.mark.*` decorators - edited error message for warnning W6403 - added py39 for testing
1 parent 097b776 commit 41a7936

14 files changed

+246
-2
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,31 @@ def yield_fixture():
9999
yield
100100
```
101101

102+
### W6402 `useless-pytest-mark-decorator`
103+
Raise when using every `@pytest.mark.*` for the fixture ([ref](https://docs.pytest.org/en/stable/reference.html#marks))
104+
```python
105+
import pytest
106+
107+
@pytest.fixture
108+
def awesome_fixture():
109+
...
110+
111+
@pytest.fixture
112+
@pytest.mark.usefixtures("awesome_fixture")
113+
def another_awesome_fixture():
114+
...
115+
```
116+
117+
### W6403 `deprecated-positional-argument-for-pytest-fixture`
118+
Raise when using deprecated positional arguments for fixture decorator
119+
```python
120+
import pytest
121+
122+
@pytest.fixture("module")
123+
def awesome_fixture():
124+
...
125+
```
126+
102127
## Changelog
103128

104129
See [CHANGELOG](CHANGELOG.md).

pylint_pytest/checkers/fixture.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
from pylint.checkers.variables import VariablesChecker
66
from pylint.interfaces import IAstroidChecker
77
import pytest
8-
from ..utils import _can_use_fixture, _is_same_module, _is_pytest_mark_usefixtures, _is_pytest_fixture
8+
from ..utils import (
9+
_can_use_fixture,
10+
_is_pytest_mark,
11+
_is_pytest_mark_usefixtures,
12+
_is_pytest_fixture,
13+
_is_same_module,
14+
)
915
from . import BasePytestChecker
1016

1117

@@ -25,6 +31,20 @@ class FixtureChecker(BasePytestChecker):
2531
'deprecated-pytest-yield-fixture',
2632
'Used when using a deprecated pytest decorator that has been deprecated in pytest-3.0'
2733
),
34+
'W6402': (
35+
'Using useless `@pytest.mark.*` decorator for fixtures',
36+
'useless-pytest-mark-decorator',
37+
(
38+
'@pytest.mark.* decorators can\'t by applied to fixtures. '
39+
'Take a look at: https://docs.pytest.org/en/stable/reference.html#marks'
40+
),
41+
),
42+
'W6403': (
43+
'Using a deprecated positional arguments for fixture',
44+
'deprecated-positional-argument-for-pytest-fixture',
45+
'Pass scope as a kwarg, not positional arg, which is deprecated in future pytest.'
46+
'Take a look at: https://docs.pytest.org/en/stable/deprecations.html#pytest-fixture-arguments-are-keyword-only',
47+
),
2848
}
2949

3050
_pytest_fixtures = {}
@@ -91,6 +111,41 @@ def visit_module(self, node):
91111
# restore output devices
92112
sys.stdout, sys.stderr = stdout, stderr
93113

114+
def visit_decorators(self, node):
115+
"""
116+
Walk through all decorators on functions.
117+
Tries to find cases:
118+
When uses `@pytest.fixture` with `scope` as positional argument (deprecated)
119+
https://docs.pytest.org/en/stable/deprecations.html#pytest-fixture-arguments-are-keyword-only
120+
>>> @pytest.fixture("module")
121+
>>> def awesome_fixture(): ...
122+
Instead
123+
>>> @pytest.fixture(scope="module")
124+
>>> def awesome_fixture(): ...
125+
When uses `@pytest.mark.usefixtures` for fixture (useless because didn't work)
126+
https://docs.pytest.org/en/stable/reference.html#marks
127+
>>> @pytest.mark.usefixtures("another_fixture")
128+
>>> @pytest.fixture
129+
>>> def awesome_fixture(): ...
130+
Parameters
131+
----------
132+
node : astroid.scoped_nodes.Decorators
133+
"""
134+
uses_fixture_deco, uses_mark_deco = False, False
135+
for decorator in node.nodes:
136+
try:
137+
if _is_pytest_fixture(decorator) and isinstance(decorator, astroid.Call) and decorator.args:
138+
self.add_message(
139+
'deprecated-positional-argument-for-pytest-fixture', node=decorator
140+
)
141+
uses_fixture_deco |= _is_pytest_fixture(decorator)
142+
uses_mark_deco |= _is_pytest_mark(decorator)
143+
except AttributeError:
144+
# ignore any parse exceptions
145+
pass
146+
if uses_mark_deco and uses_fixture_deco:
147+
self.add_message("useless-pytest-mark-decorator", node=node)
148+
94149
def visit_functiondef(self, node):
95150
'''
96151
- save invoked fixtures for later use

pylint_pytest/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ def _is_pytest_mark_usefixtures(decorator):
1515
return False
1616

1717

18+
def _is_pytest_mark(decorator):
19+
try:
20+
deco = decorator # as attribute `@pytest.mark.trylast`
21+
if isinstance(decorator, astroid.Call):
22+
deco = decorator.func # as function `@pytest.mark.skipif(...)`
23+
if deco.expr.attrname == 'mark' and deco.expr.expr.name == 'pytest':
24+
return True
25+
except AttributeError:
26+
return False
27+
28+
1829
def _is_pytest_fixture(decorator, fixture=True, yield_fixture=True):
1930
attr = None
2031
to_check = set()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pytest
2+
3+
4+
@pytest.fixture('function')
5+
def some_fixture():
6+
return 'ok'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pytest
2+
3+
4+
@pytest.fixture(scope='function')
5+
def some_fixture():
6+
return 'ok'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def some_fixture():
6+
return 'ok'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def first():
6+
return "OK"
7+
8+
9+
@pytest.mark.usefixtures("first")
10+
class TestFirst:
11+
@staticmethod
12+
def do():
13+
return True
14+
15+
def test_first(self):
16+
assert self.do() is True
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def first():
6+
return "OK"
7+
8+
9+
@pytest.fixture
10+
@pytest.mark.usefixtures("first")
11+
def second():
12+
return "NOK"
13+
14+
15+
@pytest.mark.usefixtures("first")
16+
@pytest.fixture
17+
def third():
18+
return "NOK"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import pytest
2+
3+
4+
@pytest.fixture(scope="session")
5+
def first():
6+
return "OK"
7+
8+
9+
@pytest.fixture(scope="function")
10+
@pytest.mark.usefixtures("first")
11+
def second():
12+
return "NOK"
13+
14+
15+
@pytest.mark.usefixtures("first")
16+
@pytest.fixture(scope="function")
17+
def third():
18+
return "NOK"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def first():
6+
return "OK"
7+
8+
9+
@pytest.mark.usefixtures("first")
10+
def test_first():
11+
pass

0 commit comments

Comments
 (0)