Reputation: 213
I have seen from the pytest docs that we can apply multiple markers at once on the Class or module level. I didn't find documentation for doing it at the test function level. Has anybody done this before with success?
I would like to ideally do this as a list of markers as being done in the above doc for Classes, for example (quoting from the docs):
class TestClass:
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
So, the pytest documentation talks about using pytestmark
to specify the markers at the class and module level. However, it doesn't talk about having something similar at the test function level. I would have to specify the markers individually on top of test functions to get them marked with each one of them. This makes the test code look a little clunky with the increasing number of markers on top of test functions.
test_example.py:
pytestmark = [class1, class2]
class TestFeature(TestCase):
@pytest.mark.marker1
@pytest.mark.marker2
@pytest.mark.marker3
def test_function(self):
assert True
Upvotes: 21
Views: 30567
Reputation: 3055
For functions you just repeat the decorator:
@pytest.mark.webtest
@pytest.mark.slowtest
def test_something(...):
...
If you want to reuse that for several tests you should remember that decorators are just functions returning decorated thing, so several decorators is just a composition:
def compose_decos(decos):
def composition(func):
for deco in reversed(decos):
func = deco(func)
return func
return composition
all_marks = compose_decos(pytest.mark.webtest, pytest.mark.slowtest)
@all_marks
def test_something(...):
...
Or you can use general purpose composition such as my funcy library has:
from funcy import compose
all_marks = compose(pytest.mark.webtest, pytest.mark.slowtest)
Note that this way you can compose any decorators, not only pytest marks.
Upvotes: 35
Reputation: 17041
Haven't tried this myself. However, from a quick look at the source, I think class MarkDecorator
is what you want. Try:
mark_names=["marker1", "marker2", "marker3"]
my_marks = pytest.MarkDecorator(*mark_names)
marked_test_function = my_marks(test_function)
The *mark_names
just unpacks mark_names
into the constructor arguments of MarkDecorator
. MarkDecorator.__call__
then applies the stored marks (self.args
) to the parameter, here test_function
, to provide a marked test function.
You can also use def unmarked_test_function() ...
and test_function=my_marks(unmarked_test_function)
so you don't have to change names.
Added explanation: I got this from pytest.mark
, which turns out to be a MarkGenerator
singleton. MarkGenerator
creates MarkDecorator
classes, which are then applied as decorators. The above code simulates that process manually, stuffing multiple markers.
Upvotes: 2