Skip to content

Commit 9642427

Browse files
authored
Improve mark docs (#5265)
Improve mark docs
2 parents c8f7e50 + e44a2ef commit 9642427

File tree

4 files changed

+151
-123
lines changed

4 files changed

+151
-123
lines changed

doc/en/example/markers.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
Working with custom markers
55
=================================================
66

7-
Here are some example using the :ref:`mark` mechanism.
7+
Here are some examples using the :ref:`mark` mechanism.
8+
9+
.. _`mark run`:
810

911
Marking test functions and selecting them for a run
1012
----------------------------------------------------

doc/en/historical-notes.rst

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,117 @@ Historical Notes
44
This page lists features or behavior from previous versions of pytest which have changed over the years. They are
55
kept here as a historical note so users looking at old code can find documentation related to them.
66

7+
8+
.. _marker-revamp:
9+
10+
Marker revamp and iteration
11+
---------------------------
12+
13+
.. versionchanged:: 3.6
14+
15+
pytest's marker implementation traditionally worked by simply updating the ``__dict__`` attribute of functions to cumulatively add markers. As a result, markers would unintentionally be passed along class hierarchies in surprising ways. Further, the API for retrieving them was inconsistent, as markers from parameterization would be stored differently than markers applied using the ``@pytest.mark`` decorator and markers added via ``node.add_marker``.
16+
17+
This state of things made it technically next to impossible to use data from markers correctly without having a deep understanding of the internals, leading to subtle and hard to understand bugs in more advanced usages.
18+
19+
Depending on how a marker got declared/changed one would get either a ``MarkerInfo`` which might contain markers from sibling classes,
20+
``MarkDecorators`` when marks came from parameterization or from a ``node.add_marker`` call, discarding prior marks. Also ``MarkerInfo`` acts like a single mark, when it in fact represents a merged view on multiple marks with the same name.
21+
22+
On top of that markers were not accessible in the same way for modules, classes, and functions/methods.
23+
In fact, markers were only accessible in functions, even if they were declared on classes/modules.
24+
25+
A new API to access markers has been introduced in pytest 3.6 in order to solve the problems with
26+
the initial design, providing the :func:`_pytest.nodes.Node.iter_markers` method to iterate over
27+
markers in a consistent manner and reworking the internals, which solved a great deal of problems
28+
with the initial design.
29+
30+
31+
.. _update marker code:
32+
33+
Updating code
34+
~~~~~~~~~~~~~
35+
36+
The old ``Node.get_marker(name)`` function is considered deprecated because it returns an internal ``MarkerInfo`` object
37+
which contains the merged name, ``*args`` and ``**kwargs`` of all the markers which apply to that node.
38+
39+
In general there are two scenarios on how markers should be handled:
40+
41+
1. Marks overwrite each other. Order matters but you only want to think of your mark as a single item. E.g.
42+
``log_level('info')`` at a module level can be overwritten by ``log_level('debug')`` for a specific test.
43+
44+
In this case, use ``Node.get_closest_marker(name)``:
45+
46+
.. code-block:: python
47+
48+
# replace this:
49+
marker = item.get_marker("log_level")
50+
if marker:
51+
level = marker.args[0]
52+
53+
# by this:
54+
marker = item.get_closest_marker("log_level")
55+
if marker:
56+
level = marker.args[0]
57+
58+
2. Marks compose in an additive manner. E.g. ``skipif(condition)`` marks mean you just want to evaluate all of them,
59+
order doesn't even matter. You probably want to think of your marks as a set here.
60+
61+
In this case iterate over each mark and handle their ``*args`` and ``**kwargs`` individually.
62+
63+
.. code-block:: python
64+
65+
# replace this
66+
skipif = item.get_marker("skipif")
67+
if skipif:
68+
for condition in skipif.args:
69+
# eval condition
70+
...
71+
72+
# by this:
73+
for skipif in item.iter_markers("skipif"):
74+
condition = skipif.args[0]
75+
# eval condition
76+
77+
78+
If you are unsure or have any questions, please consider opening
79+
`an issue <https://github.com/pytest-dev/pytest/issues>`_.
80+
81+
Related issues
82+
~~~~~~~~~~~~~~
83+
84+
Here is a non-exhaustive list of issues fixed by the new implementation:
85+
86+
* Marks don't pick up nested classes (`#199 <https://github.com/pytest-dev/pytest/issues/199>`_).
87+
88+
* Markers stain on all related classes (`#568 <https://github.com/pytest-dev/pytest/issues/568>`_).
89+
90+
* Combining marks - args and kwargs calculation (`#2897 <https://github.com/pytest-dev/pytest/issues/2897>`_).
91+
92+
* ``request.node.get_marker('name')`` returns ``None`` for markers applied in classes (`#902 <https://github.com/pytest-dev/pytest/issues/902>`_).
93+
94+
* Marks applied in parametrize are stored as markdecorator (`#2400 <https://github.com/pytest-dev/pytest/issues/2400>`_).
95+
96+
* Fix marker interaction in a backward incompatible way (`#1670 <https://github.com/pytest-dev/pytest/issues/1670>`_).
97+
98+
* Refactor marks to get rid of the current "marks transfer" mechanism (`#2363 <https://github.com/pytest-dev/pytest/issues/2363>`_).
99+
100+
* Introduce FunctionDefinition node, use it in generate_tests (`#2522 <https://github.com/pytest-dev/pytest/issues/2522>`_).
101+
102+
* Remove named marker attributes and collect markers in items (`#891 <https://github.com/pytest-dev/pytest/issues/891>`_).
103+
104+
* skipif mark from parametrize hides module level skipif mark (`#1540 <https://github.com/pytest-dev/pytest/issues/1540>`_).
105+
106+
* skipif + parametrize not skipping tests (`#1296 <https://github.com/pytest-dev/pytest/issues/1296>`_).
107+
108+
* Marker transfer incompatible with inheritance (`#535 <https://github.com/pytest-dev/pytest/issues/535>`_).
109+
110+
More details can be found in the `original PR <https://github.com/pytest-dev/pytest/pull/3317>`_.
111+
112+
.. note::
113+
114+
in a future major relase of pytest we will introduce class based markers,
115+
at which point markers will no longer be limited to instances of :py:class:`Mark`.
116+
117+
7118
cache plugin integrated into the core
8119
-------------------------------------
9120

doc/en/mark.rst

Lines changed: 35 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -15,151 +15,64 @@ some builtin markers, for example:
1515
to the same test function.
1616

1717
It's easy to create custom markers or to apply markers
18-
to whole test classes or modules. See :ref:`mark examples` for examples
19-
which also serve as documentation.
18+
to whole test classes or modules. Those markers can be used by plugins, and also
19+
are commonly used to :ref:`select tests <mark run>` on the command-line with the ``-m`` option.
20+
21+
See :ref:`mark examples` for examples which also serve as documentation.
2022

2123
.. note::
2224

2325
Marks can only be applied to tests, having no effect on
2426
:ref:`fixtures <fixtures>`.
2527

2628

27-
.. _unknown-marks:
28-
29-
Raising errors on unknown marks
30-
-------------------------------
29+
Registering marks
30+
-----------------
3131

32-
Unknown marks applied with the ``@pytest.mark.name_of_the_mark`` decorator
33-
will always emit a warning, in order to avoid silently doing something
34-
surprising due to mis-typed names. You can disable the warning for custom
35-
marks by registering them in ``pytest.ini`` like this:
32+
You can register custom marks in your ``pytest.ini`` file like this:
3633

3734
.. code-block:: ini
3835
3936
[pytest]
4037
markers =
41-
slow
38+
slow: marks tests as slow (deselect with '-m "not slow"')
4239
serial
4340
44-
When the ``--strict-markers`` command-line flag is passed, any unknown marks applied
45-
with the ``@pytest.mark.name_of_the_mark`` decorator will trigger an error.
46-
Marks added by pytest or by a plugin instead of the decorator will not trigger
47-
this error. To enforce validation of markers, add ``--strict-markers`` to ``addopts``:
48-
49-
.. code-block:: ini
50-
51-
[pytest]
52-
addopts = --strict-markers
53-
markers =
54-
slow
55-
serial
56-
57-
Third-party plugins should always :ref:`register their markers <registering-markers>`
58-
so that they appear in pytest's help text and do not emit warnings.
59-
60-
61-
.. _marker-revamp:
62-
63-
Marker revamp and iteration
64-
---------------------------
65-
66-
67-
68-
pytest's marker implementation traditionally worked by simply updating the ``__dict__`` attribute of functions to cumulatively add markers. As a result, markers would unintentionally be passed along class hierarchies in surprising ways. Further, the API for retrieving them was inconsistent, as markers from parameterization would be stored differently than markers applied using the ``@pytest.mark`` decorator and markers added via ``node.add_marker``.
69-
70-
This state of things made it technically next to impossible to use data from markers correctly without having a deep understanding of the internals, leading to subtle and hard to understand bugs in more advanced usages.
71-
72-
Depending on how a marker got declared/changed one would get either a ``MarkerInfo`` which might contain markers from sibling classes,
73-
``MarkDecorators`` when marks came from parameterization or from a ``node.add_marker`` call, discarding prior marks. Also ``MarkerInfo`` acts like a single mark, when it in fact represents a merged view on multiple marks with the same name.
74-
75-
On top of that markers were not accessible the same way for modules, classes, and functions/methods.
76-
In fact, markers were only accessible in functions, even if they were declared on classes/modules.
77-
78-
A new API to access markers has been introduced in pytest 3.6 in order to solve the problems with the initial design, providing :func:`_pytest.nodes.Node.iter_markers` method to iterate over markers in a consistent manner and reworking the internals, which solved great deal of problems with the initial design.
79-
80-
81-
.. _update marker code:
82-
83-
Updating code
84-
~~~~~~~~~~~~~
85-
86-
The old ``Node.get_marker(name)`` function is considered deprecated because it returns an internal ``MarkerInfo`` object
87-
which contains the merged name, ``*args`` and ``**kwargs`` of all the markers which apply to that node.
88-
89-
In general there are two scenarios on how markers should be handled:
41+
Note that everything after the ``:`` is an optional description.
9042

91-
1. Marks overwrite each other. Order matters but you only want to think of your mark as a single item. E.g.
92-
``log_level('info')`` at a module level can be overwritten by ``log_level('debug')`` for a specific test.
43+
Alternatively, you can register new markers programatically in a
44+
:ref:`pytest_configure <initialization-hooks>` hook:
9345

94-
In this case, use ``Node.get_closest_marker(name)``:
46+
.. code-block:: python
9547
96-
.. code-block:: python
48+
def pytest_configure(config):
49+
config.addinivalue_line(
50+
"markers", "env(name): mark test to run only on named environment"
51+
)
9752
98-
# replace this:
99-
marker = item.get_marker("log_level")
100-
if marker:
101-
level = marker.args[0]
10253
103-
# by this:
104-
marker = item.get_closest_marker("log_level")
105-
if marker:
106-
level = marker.args[0]
54+
Registered marks appear in pytest's help text and do not emit warnings (see the next section). It
55+
is recommended that third-party plugins always :ref:`register their markers <registering-markers>`.
10756

108-
2. Marks compose in an additive manner. E.g. ``skipif(condition)`` marks mean you just want to evaluate all of them,
109-
order doesn't even matter. You probably want to think of your marks as a set here.
110-
111-
In this case iterate over each mark and handle their ``*args`` and ``**kwargs`` individually.
112-
113-
.. code-block:: python
114-
115-
# replace this
116-
skipif = item.get_marker("skipif")
117-
if skipif:
118-
for condition in skipif.args:
119-
# eval condition
120-
...
121-
122-
# by this:
123-
for skipif in item.iter_markers("skipif"):
124-
condition = skipif.args[0]
125-
# eval condition
126-
127-
128-
If you are unsure or have any questions, please consider opening
129-
`an issue <https://github.com/pytest-dev/pytest/issues>`_.
130-
131-
Related issues
132-
~~~~~~~~~~~~~~
133-
134-
Here is a non-exhaustive list of issues fixed by the new implementation:
135-
136-
* Marks don't pick up nested classes (`#199 <https://github.com/pytest-dev/pytest/issues/199>`_).
137-
138-
* Markers stain on all related classes (`#568 <https://github.com/pytest-dev/pytest/issues/568>`_).
139-
140-
* Combining marks - args and kwargs calculation (`#2897 <https://github.com/pytest-dev/pytest/issues/2897>`_).
141-
142-
* ``request.node.get_marker('name')`` returns ``None`` for markers applied in classes (`#902 <https://github.com/pytest-dev/pytest/issues/902>`_).
143-
144-
* Marks applied in parametrize are stored as markdecorator (`#2400 <https://github.com/pytest-dev/pytest/issues/2400>`_).
145-
146-
* Fix marker interaction in a backward incompatible way (`#1670 <https://github.com/pytest-dev/pytest/issues/1670>`_).
147-
148-
* Refactor marks to get rid of the current "marks transfer" mechanism (`#2363 <https://github.com/pytest-dev/pytest/issues/2363>`_).
149-
150-
* Introduce FunctionDefinition node, use it in generate_tests (`#2522 <https://github.com/pytest-dev/pytest/issues/2522>`_).
151-
152-
* Remove named marker attributes and collect markers in items (`#891 <https://github.com/pytest-dev/pytest/issues/891>`_).
153-
154-
* skipif mark from parametrize hides module level skipif mark (`#1540 <https://github.com/pytest-dev/pytest/issues/1540>`_).
57+
.. _unknown-marks:
15558

156-
* skipif + parametrize not skipping tests (`#1296 <https://github.com/pytest-dev/pytest/issues/1296>`_).
59+
Raising errors on unknown marks
60+
-------------------------------
15761

158-
* Marker transfer incompatible with inheritance (`#535 <https://github.com/pytest-dev/pytest/issues/535>`_).
62+
Unregistered marks applied with the ``@pytest.mark.name_of_the_mark`` decorator
63+
will always emit a warning in order to avoid silently doing something
64+
surprising due to mis-typed names. As described in the previous section, you can disable
65+
the warning for custom marks by registering them in your ``pytest.ini`` file or
66+
using a custom ``pytest_configure`` hook.
15967

160-
More details can be found in the `original PR <https://github.com/pytest-dev/pytest/pull/3317>`_.
68+
When the ``--strict-markers`` command-line flag is passed, any unknown marks applied
69+
with the ``@pytest.mark.name_of_the_mark`` decorator will trigger an error. You can
70+
enforce this validation in your project by adding ``--strict-markers`` to ``addopts``:
16171

162-
.. note::
72+
.. code-block:: ini
16373
164-
in a future major relase of pytest we will introduce class based markers,
165-
at which point markers will no longer be limited to instances of :py:class:`Mark`.
74+
[pytest]
75+
addopts = --strict-markers
76+
markers =
77+
slow: marks tests as slow (deselect with '-m "not slow"')
78+
serial

doc/en/reference.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,8 @@ Bootstrapping hooks called for plugins registered early enough (internal and set
581581
.. autofunction:: pytest_cmdline_parse
582582
.. autofunction:: pytest_cmdline_main
583583

584+
.. _`initialization-hooks`:
585+
584586
Initialization hooks
585587
~~~~~~~~~~~~~~~~~~~~
586588

0 commit comments

Comments
 (0)