Skip to content

Commit d1b0d1a

Browse files
committed
Mostly implement pytest4 compatibility
1 parent 674f238 commit d1b0d1a

File tree

2 files changed

+119
-25
lines changed

2 files changed

+119
-25
lines changed

parameterized/parameterized.py

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
class SkipTest(Exception):
2020
pass
2121

22+
try:
23+
import pytest
24+
except ImportError:
25+
pytest = None
26+
2227
PY3 = sys.version_info[0] == 3
2328
PY2 = sys.version_info[0] == 2
2429

@@ -352,6 +357,94 @@ def __init__(self, input, doc_func=None, skip_on_empty=False):
352357
def __call__(self, test_func):
353358
self.assert_not_in_testcase_subclass()
354359

360+
input = self.get_input()
361+
wrapper = self._wrap_test_func(test_func, input)
362+
wrapper.parameterized_input = input
363+
wrapper.parameterized_func = test_func
364+
test_func.__name__ = "_parameterized_original_%s" %(test_func.__name__, )
365+
366+
return wrapper
367+
368+
def _wrap_test_func(self, test_func, input):
369+
""" Wraps a test function so that it will appropriately handle
370+
parameterization.
371+
372+
In the general case, the wrapper will enumerate the input, yielding
373+
test cases.
374+
375+
In the case of pytest4, the wrapper will use
376+
``@pytest.mark.parametrize`` to parameterize the test function. """
377+
378+
if not input:
379+
if not self.skip_on_empty:
380+
raise ValueError(
381+
"Parameters iterable is empty (hint: use "
382+
"`parameterized([], skip_on_empty=True)` to skip "
383+
"this test when the input is empty)"
384+
)
385+
return wraps(test_func)(skip_on_empty_helper)
386+
387+
if pytest and pytest.__version__ > '4.0.0':
388+
Undefined = object()
389+
test_func_wrapped = test_func
390+
test_func_real, mock_patchings = unwrap_mock_patch_func(test_func_wrapped)
391+
func_argspec = getargspec(test_func_real)
392+
393+
func_args = func_argspec.args
394+
if mock_patchings:
395+
func_args = func_args[:-len(mock_patchings)]
396+
397+
func_args_no_self = func_args
398+
if func_args_no_self[:1] == ["self"]:
399+
func_args_no_self = func_args_no_self[1:]
400+
401+
args_with_default = dict(
402+
(arg, Undefined)
403+
for arg in func_args_no_self
404+
)
405+
for (arg, default) in zip(reversed(func_args_no_self), reversed(func_argspec.defaults or [])):
406+
args_with_default[arg] = default
407+
408+
pytest_params = []
409+
for i in input:
410+
p = dict(args_with_default)
411+
for (arg, val) in zip(func_args_no_self, i.args):
412+
p[arg] = val
413+
p.update(i.kwargs)
414+
415+
# Sanity check: all arguments should now be defined
416+
if any(v is Undefined for v in p.values()):
417+
raise ValueError(
418+
"When parameterizing function %r: no value for arguments: %s" %(
419+
test_func,
420+
", ".join(
421+
repr(arg)
422+
for (arg, val) in p.items()
423+
if val is Undefined
424+
),
425+
)
426+
)
427+
428+
pytest_params.append(pytest.param(*[
429+
p.get(arg) for arg in func_args_no_self
430+
]))
431+
432+
namespace = {
433+
"__test_func": test_func_wrapped,
434+
}
435+
wrapper_name = "parameterized_pytest_wrapper_%s" %(test_func.__name__, )
436+
exec(
437+
"def %s(%s): return __test_func(%s)" %(
438+
wrapper_name,
439+
",".join(func_args),
440+
",".join(func_args),
441+
),
442+
namespace,
443+
namespace,
444+
)
445+
446+
return pytest.mark.parametrize(",".join(func_args_no_self), pytest_params)(namespace[wrapper_name])
447+
355448
@wraps(test_func)
356449
def wrapper(test_self=None):
357450
test_cls = test_self and type(test_self)
@@ -366,7 +459,7 @@ def wrapper(test_self=None):
366459
) %(test_self, ))
367460

368461
original_doc = wrapper.__doc__
369-
for num, args in enumerate(wrapper.parameterized_input):
462+
for num, args in enumerate(input):
370463
p = param.from_decorator(args)
371464
unbound_func, nose_tuple = self.param_as_nose_tuple(test_self, test_func, num, p)
372465
try:
@@ -383,21 +476,6 @@ def wrapper(test_self=None):
383476
if test_self is not None:
384477
delattr(test_cls, test_func.__name__)
385478
wrapper.__doc__ = original_doc
386-
387-
input = self.get_input()
388-
if not input:
389-
if not self.skip_on_empty:
390-
raise ValueError(
391-
"Parameters iterable is empty (hint: use "
392-
"`parameterized([], skip_on_empty=True)` to skip "
393-
"this test when the input is empty)"
394-
)
395-
wrapper = wraps(test_func)(skip_on_empty_helper)
396-
397-
wrapper.parameterized_input = input
398-
wrapper.parameterized_func = test_func
399-
test_func.__name__ = "_parameterized_original_%s" %(test_func.__name__, )
400-
401479
return wrapper
402480

403481
def param_as_nose_tuple(self, test_self, func, num, p):
@@ -618,6 +696,11 @@ def decorator(base_class):
618696

619697
return decorator
620698

699+
def unwrap_mock_patch_func(f):
700+
if not hasattr(f, "patchings"):
701+
return (f, [])
702+
real_func, patchings = unwrap_mock_patch_func(f.__wrapped__)
703+
return (real_func, patchings + f.patchings)
621704

622705
def get_class_name_suffix(params_dict):
623706
if "name" in params_dict:

parameterized/test.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def expect(skip, tests=None):
4040

4141
test_params = [
4242
(42, ),
43+
(42, "bar_val"),
4344
"foo0",
4445
param("foo1"),
4546
param("foo2", bar=42),
@@ -50,6 +51,7 @@ def expect(skip, tests=None):
5051
"test_naked_function('foo1', bar=None)",
5152
"test_naked_function('foo2', bar=42)",
5253
"test_naked_function(42, bar=None)",
54+
"test_naked_function(42, bar='bar_val')",
5355
])
5456

5557
@parameterized(test_params)
@@ -63,6 +65,7 @@ class TestParameterized(object):
6365
"test_instance_method('foo1', bar=None)",
6466
"test_instance_method('foo2', bar=42)",
6567
"test_instance_method(42, bar=None)",
68+
"test_instance_method(42, bar='bar_val')",
6669
])
6770

6871
@parameterized(test_params)
@@ -95,10 +98,16 @@ def test_setup(self, count, *a):
9598
missing_tests.remove("test_setup(%s)" %(self.actual_order, ))
9699

97100

98-
def custom_naming_func(custom_tag):
101+
def custom_naming_func(custom_tag, kw_name):
99102
def custom_naming_func(testcase_func, param_num, param):
100-
return testcase_func.__name__ + ('_%s_name_' % custom_tag) + str(param.args[0])
101-
103+
return (
104+
testcase_func.__name__ +
105+
'_%s_name_' %(custom_tag, ) +
106+
str(param.args[0]) +
107+
# This ... is a bit messy, to properly handle the values in
108+
# `test_params`, but ... it should work.
109+
'_%s' %(param.args[1] if len(param.args) > 1 else param.kwargs.get(kw_name), )
110+
)
102111
return custom_naming_func
103112

104113

@@ -214,27 +223,29 @@ class TestParamerizedOnTestCase(TestCase):
214223
"test_on_TestCase('foo1', bar=None)",
215224
"test_on_TestCase('foo2', bar=42)",
216225
"test_on_TestCase(42, bar=None)",
226+
"test_on_TestCase(42, bar='bar_val')",
217227
])
218228

219229
@parameterized.expand(test_params)
220230
def test_on_TestCase(self, foo, bar=None):
221231
missing_tests.remove("test_on_TestCase(%r, bar=%r)" %(foo, bar))
222232

223233
expect([
224-
"test_on_TestCase2_custom_name_42(42, bar=None)",
225-
"test_on_TestCase2_custom_name_foo0('foo0', bar=None)",
226-
"test_on_TestCase2_custom_name_foo1('foo1', bar=None)",
227-
"test_on_TestCase2_custom_name_foo2('foo2', bar=42)",
234+
"test_on_TestCase2_custom_name_42_None(42, bar=None)",
235+
"test_on_TestCase2_custom_name_42_bar_val(42, bar='bar_val')",
236+
"test_on_TestCase2_custom_name_foo0_None('foo0', bar=None)",
237+
"test_on_TestCase2_custom_name_foo1_None('foo1', bar=None)",
238+
"test_on_TestCase2_custom_name_foo2_42('foo2', bar=42)",
228239
])
229240

230241
@parameterized.expand(test_params,
231-
name_func=custom_naming_func("custom"))
242+
name_func=custom_naming_func("custom", "bar"))
232243
def test_on_TestCase2(self, foo, bar=None):
233244
stack = inspect.stack()
234245
frame = stack[1]
235246
frame_locals = frame[0].f_locals
236247
nose_test_method_name = frame_locals['a'][0]._testMethodName
237-
expected_name = "test_on_TestCase2_custom_name_" + str(foo)
248+
expected_name = "test_on_TestCase2_custom_name_" + str(foo) + "_" + str(bar)
238249
assert_equal(nose_test_method_name, expected_name,
239250
"Test Method name '%s' did not get customized to expected: '%s'" %
240251
(nose_test_method_name, expected_name))

0 commit comments

Comments
 (0)