writing decorator for pytest test method

  • Last Update :
  • Techknowledgy :

Chaining decorators is not the simplest thing to do. One solution might be to separate the two decorators. Keep the classmethod but move decorator.decorator to the end:

@classmethod
def this_is_decorator(cls, f):
   def wrapper(self, * args, ** kw):
   with self.baz as magic:
   with magic.fooz as more_magic:
   blah = more_magic.much_more_magic() # repetative bleh
return f(self, * args)
return decorator.decorator(wrapper, f)

After some tweaking and realizing that I need to pass a parameter to decorator I choosed to write it as a class:

class ThisIsDecorator(object):
   def __init__(self, param):
   self.param = param # Parameter may vary with the
function being decorated
def __call__(self, fn):
   wraps(fn) #[1]
def wrapper(fn, fn_self, * args): #[2] fn_self refers to original self param from
function fn(test_a_lot)[2]
with fn_self.baz as fn_self.magic: # I pass magic to fn_self to make magic accesible in function fn(test_a_lot)
with fn_self.magic.fooz as more_magic:
   blah = self.param.much_more_magic() # repetative bleh
return fn(fn_self, * args)
return decorator.decorator(wrapper, fn)

Original version (without passing a parameter):

 @classmethod
 def this_is_decorator(cls, fn):
    @wraps(fn)
 def wrapper(fn, fn_self, * args):
    with fn_self.baz as fn_self.magic:
    with fn_self.magic.fooz as more_magic:
    blah = more_magic.much_more_magic() # repetative bleh
 return fn(fn_self, * args)
 return decorator.decorator(wrapper, fn)

But considering that this_is_decorator is a class method, it's not clear to me that this is what you want. I'm guessing that you may want something more like this:

from decorator
import decorator
@decorator
def mydecorator(f):
   def wrapper(cls, * args, ** kw):
   #...logging, reporting, caching, whatever
return f( * args, ** kw)
return wrapper

class MyClass(object):
   @classmethod
@mydecorator
def myclsmethod(a, b, c):
   # no cls or self value accepted here;
this is a
function not a method
#...

Since you have an additional need to preserve the argument list exactly, so that even the names of the arguments are preserved in the decorated function, except with an extra initial argument cls, then you could implement mydecorator this way:

from decorator
import decorator
from inspect
import getargspec

@decorator
def mydecorator(func):
   result = [None] # necessary so exec can "return"
objects
namespace = {
   'f': func,
   'result': result
}
source = []
add = lambda indent, line: source.append(' ' * indent + line) # shorthand
arglist = ', '.join(getargspec(func).args) # this does not cover keyword or
default args
add(0, 'def wrapper(cls, %s):' % (arglist, ))
add(2, 'return f(%s)' % (arglist, ))
add(0, 'result[0] = wrapper') # this is how to "return"
something from exec
exec '\n'.join(source) in namespace
return result[0] # this is wrapper

Suggestion : 2

Python decorators can be easily tested by integration tests, but it is good practice to create some unit tests for them as well. You can easily unit test a decorator by applying it to a dummy function or mock. There is a nice trick to “bypass” a decorator as well.,Writing an integration test is quite easy, instead of testing the decorator and the decorated function separately, we just test the wrapped function.,This is quite concise, but it feels a little bit hacky. Good tests also serve as documentation for your code, but if I look at the test above it is not obvious at first glance that we are testing a decorator.,It is quite common that you want to do the opposite - test a decorated function, but do it without running the decorator code.

def triple(func):
   def wrapper_func( * args, ** kwargs):
   return func( * args, ** kwargs) * 3
return wrapper_func
def example(x):
   return x

wrapped = triple(example)

example(1) # returns 1
wrapped(1) # returns 3
@triple
def example(x):
   return x

example(1) # returns 3
example.__name__ # returns 'wrapper_func'
from functools
import wraps
def triple(func):
   @wraps(func)
def wrapper_func( * args, ** kwargs):
   return func( * args, ** kwargs) * 3
return wrapper_func
example.__name__ # returns 'example'

Suggestion : 3

Sean Hammond, Mar 2017, Mar 2017 The Problem with Mocks , Mar 2017 The Problem with Mocks , Mar 2017 Python’s unittest.mock

@pytest.mark.usefixtures('routes')
def test_something():
   ...
@pytest.mark.usefixtures('storage')
class TestCreate(object):

   def test_something(self):
   # The storage fixture will be used in this test, even though the test
# method itself didn 't declare it.

# If some tests _do_ want to use the mock storage object in their test
# method bodies, they can just take the fixture as an argument as usual.
def test_it_creates_the_annotation_in_storage(self, storage):
   ...#(Call the create() view to create an annotation)

# Test that it would have saved the annotation to storage.
storage.create_annotation.assert_called_once_with(...)

@pytest.fixture
def storage(self, patch):
   return patch('h.views.api.storage')

Suggestion : 4

@pytest.mark.parametrize allows one to define multiple sets of arguments and fixtures at the test function or class.,pytest_generate_tests allows one to define custom parametrization schemes or extensions.,pytest.fixture() allows one to parametrize fixture functions.,Parametrizing fixtures and test functions @pytest.mark.parametrize: parametrizing test functions Basic pytest_generate_tests example More examples

# content of test_expectation.py
import pytest

@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
   assert eval(test_input) == expected
$ pytest
   ===
   === === === === === === === === test session starts === === === === === === === === === =
   platform linux--Python 3. x.y, pytest - 6. x.y, py - 1. x.y, pluggy - 1. x.y
cachedir: $PYTHON_PREFIX / .pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 3 items

test_expectation.py..F[100 % ]

   ===
   === === === === === === === === === === FAILURES === === === === === === === === === === ===
   ____________________________ test_eval[6 * 9 - 42] _____________________________

test_input = '6*9', expected = 42

@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
   >
   assert eval(test_input) == expected
E AssertionError: assert 54 == 42
E + where 54 = eval('6*9')

test_expectation.py: 6: AssertionError ===
   === === === === === === === = short test summary info === === === === === === === === ==
   FAILED test_expectation.py::test_eval[6 * 9 - 42] - AssertionError: assert 54... ===
   === === === === === === == 1 failed, 2 passed in 0.12 s === === === === === === === ===
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
# content of test_expectation.py
import pytest

@pytest.mark.parametrize(
   "test_input,expected",
   [("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks = pytest.mark.xfail)],
)
def test_eval(test_input, expected):
   assert eval(test_input) == expected
$ pytest
   ===
   === === === === === === === === test session starts === === === === === === === === === =
   platform linux--Python 3. x.y, pytest - 6. x.y, py - 1. x.y, pluggy - 1. x.y
cachedir: $PYTHON_PREFIX / .pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 3 items

test_expectation.py..x[100 % ]

   ===
   === === === === === === == 2 passed, 1 xfailed in 0.12 s === === === === === === === ==
import pytest

@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
   pass