Make sure that setUp and tearDown methods work.
Previously setUp and tearDown would be called on the instance of the class being tested, but the methods were pulled from the instance of the class created during test enumeration. Now everything will work correctly.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,3 +19,4 @@ dist/
|
||||
Icon?
|
||||
.tox
|
||||
build/
|
||||
.cache/
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
0.6.2 (???)
|
||||
* Make sure that `setUp` and `tearDown` methods work correctly (#40)
|
||||
|
||||
0.6.1 (2017-03-21)
|
||||
* Rename package from nose-parameterized to parameterized. A
|
||||
nose-parameterized package will be released with a deprecation warning.
|
||||
|
||||
@@ -3,6 +3,7 @@ import sys
|
||||
import inspect
|
||||
import warnings
|
||||
from functools import wraps
|
||||
from types import MethodType as MethodType
|
||||
from collections import namedtuple
|
||||
|
||||
try:
|
||||
@@ -17,9 +18,6 @@ PY2 = sys.version_info[0] == 2
|
||||
|
||||
|
||||
if PY3:
|
||||
def new_instancemethod(f, *args):
|
||||
return f
|
||||
|
||||
# Python 3 doesn't have an InstanceType, so just use a dummy type.
|
||||
class InstanceType():
|
||||
pass
|
||||
@@ -27,14 +25,18 @@ if PY3:
|
||||
text_type = str
|
||||
string_types = str,
|
||||
bytes_type = bytes
|
||||
def make_method(func, instance, type):
|
||||
if instance is None:
|
||||
return func
|
||||
return MethodType(func, instance)
|
||||
else:
|
||||
import new
|
||||
new_instancemethod = new.instancemethod
|
||||
from types import InstanceType
|
||||
lzip = zip
|
||||
text_type = unicode
|
||||
bytes_type = str
|
||||
string_types = basestring,
|
||||
def make_method(func, instance, type):
|
||||
return MethodType(func, instance, type)
|
||||
|
||||
_param = namedtuple("param", "args kwargs")
|
||||
|
||||
@@ -89,9 +91,17 @@ class param(_param):
|
||||
"""
|
||||
if isinstance(args, param):
|
||||
return args
|
||||
if isinstance(args, string_types):
|
||||
elif isinstance(args, string_types):
|
||||
args = (args, )
|
||||
return cls(*args)
|
||||
try:
|
||||
return cls(*args)
|
||||
except TypeError as e:
|
||||
if "after * must be" not in str(e):
|
||||
raise
|
||||
raise TypeError(
|
||||
"Parameters must be tuples, but %r is not (hint: use '(%r, )')"
|
||||
%(args, args),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "param(*%r, **%r)" %self
|
||||
@@ -209,6 +219,48 @@ def default_name_func(func, num, p):
|
||||
name_suffix += "_" + parameterized.to_safe_name(p.args[0])
|
||||
return base_name + name_suffix
|
||||
|
||||
|
||||
_test_runner_override = None
|
||||
_test_runner_guess = False
|
||||
_test_runners = set(["unittest", "unittest2", "nose", "nose2", "pytest"])
|
||||
_test_runner_aliases = {
|
||||
"_pytest": "pytest",
|
||||
}
|
||||
|
||||
def set_test_runner(name):
|
||||
global _test_runner_override
|
||||
if name not in _test_runners:
|
||||
raise TypeError(
|
||||
"Invalid test runner: %r (must be one of: %s)"
|
||||
%(name, ", ".join(_test_runners)),
|
||||
)
|
||||
_test_runner_override = name
|
||||
|
||||
def detect_runner():
|
||||
""" Guess which test runner we're using by traversing the stack and looking
|
||||
for the first matching module. This *should* be reasonably safe, as
|
||||
it's done during test disocvery where the test runner should be the
|
||||
stack frame immediately outside. """
|
||||
if _test_runner_override is not None:
|
||||
return _test_runner_override
|
||||
global _test_runner_guess
|
||||
if _test_runner_guess is False:
|
||||
stack = inspect.stack()
|
||||
for record in reversed(stack):
|
||||
frame = record[0]
|
||||
module = frame.f_globals.get("__name__").partition(".")[0]
|
||||
if module in _test_runner_aliases:
|
||||
module = _test_runner_aliases[module]
|
||||
if module in _test_runners:
|
||||
_test_runner_guess = module
|
||||
break
|
||||
if record[1].endswith("python2.6/unittest.py"):
|
||||
_test_runner_guess = "unittest"
|
||||
break
|
||||
else:
|
||||
_test_runner_guess = None
|
||||
return _test_runner_guess
|
||||
|
||||
class parameterized(object):
|
||||
""" Parameterize a test case::
|
||||
|
||||
@@ -239,69 +291,65 @@ class parameterized(object):
|
||||
|
||||
@wraps(test_func)
|
||||
def wrapper(test_self=None):
|
||||
f = test_func
|
||||
test_cls = test_self and type(test_self)
|
||||
if test_self is not None:
|
||||
# If we are a test method (which we suppose to be true if we
|
||||
# are being passed a "self" argument), we first need to create
|
||||
# an instance method, attach it to the instance of the test
|
||||
# class, then pull it back off to turn it into a bound method.
|
||||
# If we don't do this, Nose gets cranky.
|
||||
f = self.make_bound_method(test_self, test_func)
|
||||
# Note: because nose is so very picky, the more obvious
|
||||
# ``return self.yield_nose_tuples(f)`` won't work here.
|
||||
for nose_tuple in self.yield_nose_tuples(f, wrapper):
|
||||
yield nose_tuple
|
||||
if issubclass(test_cls, InstanceType):
|
||||
raise TypeError((
|
||||
"@parameterized can't be used with old-style classes, but "
|
||||
"%r has an old-style class. Consider using a new-style "
|
||||
"class, or '@parameterized.expand' "
|
||||
"(see http://stackoverflow.com/q/54867/71522 for more "
|
||||
"information on old-style classes)."
|
||||
) %(test_self, ))
|
||||
|
||||
test_func.__name__ = "_helper_for_%s" %(test_func.__name__, )
|
||||
original_doc = wrapper.__doc__
|
||||
for num, args in enumerate(wrapper.parameterized_input):
|
||||
p = param.from_decorator(args)
|
||||
unbound_func, nose_tuple = self.param_as_nose_tuple(test_self, test_func, num, p)
|
||||
try:
|
||||
wrapper.__doc__ = nose_tuple[0].__doc__
|
||||
# Nose uses `getattr(instance, test_func.__name__)` to get
|
||||
# a method bound to the test instance (as opposed to a
|
||||
# method bound to the instance of the class created when
|
||||
# tests were being enumerated). Set a value here to make
|
||||
# sure nose can get the correct test method.
|
||||
if test_self is not None:
|
||||
setattr(test_cls, test_func.__name__, unbound_func)
|
||||
yield nose_tuple
|
||||
finally:
|
||||
if test_self is not None:
|
||||
delattr(test_cls, test_func.__name__)
|
||||
wrapper.__doc__ = original_doc
|
||||
wrapper.parameterized_input = self.get_input()
|
||||
wrapper.parameterized_func = test_func
|
||||
test_func.__name__ = "_parameterized_original_%s" %(test_func.__name__, )
|
||||
return wrapper
|
||||
|
||||
def yield_nose_tuples(self, func, wrapper):
|
||||
original_doc = wrapper.__doc__
|
||||
for num, args in enumerate(wrapper.parameterized_input):
|
||||
p = param.from_decorator(args)
|
||||
# ... then yield that as a tuple. If those steps aren't
|
||||
# followed precicely, Nose gets upset and doesn't run the test
|
||||
# or doesn't run setup methods.
|
||||
nose_tuple = self.param_as_nose_tuple(func, num, p)
|
||||
nose_func = nose_tuple[0]
|
||||
try:
|
||||
wrapper.__doc__ = nose_func.__doc__
|
||||
yield nose_tuple
|
||||
finally:
|
||||
wrapper.__doc__ = original_doc
|
||||
|
||||
def param_as_nose_tuple(self, func, num, p):
|
||||
if p.kwargs:
|
||||
nose_func = wraps(func)(lambda args, kwargs: func(*args, **kwargs))
|
||||
nose_args = (p.args, p.kwargs)
|
||||
else:
|
||||
nose_func = wraps(func)(lambda *args: func(*args))
|
||||
nose_args = p.args
|
||||
def param_as_nose_tuple(self, test_self, func, num, p):
|
||||
nose_func = wraps(func)(lambda *args: func(*args[:-1], **args[-1]))
|
||||
nose_func.__doc__ = self.doc_func(func, num, p)
|
||||
return (nose_func, ) + nose_args
|
||||
|
||||
def make_bound_method(self, instance, func):
|
||||
cls = type(instance)
|
||||
if issubclass(cls, InstanceType):
|
||||
raise TypeError((
|
||||
"@parameterized can't be used with old-style classes, but "
|
||||
"%r has an old-style class. Consider using a new-style "
|
||||
"class, or '@parameterized.expand' "
|
||||
"(see http://stackoverflow.com/q/54867/71522 for more "
|
||||
"information on old-style classes)."
|
||||
) %(instance, ))
|
||||
im_f = new_instancemethod(func, None, cls)
|
||||
setattr(cls, func.__name__, im_f)
|
||||
return getattr(instance, func.__name__)
|
||||
# Track the unbound function because we need to setattr the unbound
|
||||
# function onto the class for nose to work (see comments above), and
|
||||
# Python 3 doesn't let us pull the function out of a bound method.
|
||||
unbound_func = nose_func
|
||||
if test_self is not None:
|
||||
# Under nose on Py2 we need to return an unbound method to make
|
||||
# sure that the `self` in the method is properly shared with the
|
||||
# `self` used in `setUp` and `tearDown`. But only there. Everyone
|
||||
# else needs a bound method.
|
||||
func_self = (
|
||||
None if PY2 and detect_runner() == "nose" else
|
||||
test_self
|
||||
)
|
||||
nose_func = make_method(nose_func, func_self, type(test_self))
|
||||
return unbound_func, (nose_func, ) + p.args + (p.kwargs or {}, )
|
||||
|
||||
def assert_not_in_testcase_subclass(self):
|
||||
parent_classes = self._terrible_magic_get_defining_classes()
|
||||
if any(issubclass(cls, TestCase) for cls in parent_classes):
|
||||
raise Exception("Warning: '@parameterized' tests won't work "
|
||||
"inside subclasses of 'TestCase' - use "
|
||||
"'@parameterized.expand' instead")
|
||||
"'@parameterized.expand' instead.")
|
||||
|
||||
def _terrible_magic_get_defining_classes(self):
|
||||
""" Returns the set of parent classes of the class currently being defined.
|
||||
@@ -336,7 +384,7 @@ class parameterized(object):
|
||||
# https://github.com/wolever/nose-parameterized/pull/31)
|
||||
if not isinstance(input_values, list):
|
||||
input_values = list(input_values)
|
||||
return input_values
|
||||
return [ param.from_decorator(p) for p in input_values ]
|
||||
|
||||
@classmethod
|
||||
def expand(cls, input, name_func=None, doc_func=None, **legacy):
|
||||
@@ -376,8 +424,7 @@ class parameterized(object):
|
||||
frame_locals = frame[0].f_locals
|
||||
|
||||
paramters = cls.input_as_callable(input)()
|
||||
for num, args in enumerate(paramters):
|
||||
p = param.from_decorator(args)
|
||||
for num, p in enumerate(paramters):
|
||||
name = name_func(f, num, p)
|
||||
frame_locals[name] = cls.param_as_standalone_func(p, f, name)
|
||||
frame_locals[name].__doc__ = doc_func(f, num, p)
|
||||
|
||||
@@ -7,31 +7,24 @@ from nose.plugins.skip import SkipTest
|
||||
|
||||
from .parameterized import (
|
||||
PY3, PY2, parameterized, param, parameterized_argument_value_pairs,
|
||||
short_repr,
|
||||
short_repr, detect_runner,
|
||||
)
|
||||
|
||||
def assert_contains(haystack, needle):
|
||||
if needle not in haystack:
|
||||
raise AssertionError("%r not in %r" %(needle, haystack))
|
||||
|
||||
def detect_runner(candidates):
|
||||
for x in reversed(inspect.stack()):
|
||||
frame = x[0]
|
||||
for mod in candidates:
|
||||
frame_mod = frame.f_globals.get("__name__", "")
|
||||
if frame_mod == mod or frame_mod.startswith(mod + "."):
|
||||
return mod
|
||||
return "<unknown>"
|
||||
|
||||
runner = detect_runner(["nose", "nose2","unittest", "unittest2"])
|
||||
runner = detect_runner()
|
||||
UNITTEST = runner.startswith("unittest")
|
||||
NOSE2 = (runner == "nose2")
|
||||
PYTEST = (runner == "pytest")
|
||||
|
||||
SKIP_FLAGS = {
|
||||
"generator": UNITTEST,
|
||||
# nose2 doesn't run tests on old-style classes under Py2, so don't expect
|
||||
# these tests to run under nose2.
|
||||
"py2nose2": (PY2 and NOSE2),
|
||||
"pytest": PYTEST,
|
||||
}
|
||||
|
||||
missing_tests = set()
|
||||
@@ -44,10 +37,19 @@ def expect(skip, tests=None):
|
||||
return
|
||||
missing_tests.update(tests)
|
||||
|
||||
def pytest_skip(reason):
|
||||
try:
|
||||
import pytest
|
||||
except ImportError:
|
||||
pytest = None
|
||||
|
||||
def pytest_skip_helper(f):
|
||||
if not pytest:
|
||||
return f
|
||||
return pytest.mark.skip(reason=reason)(f)
|
||||
|
||||
return pytest_skip_helper
|
||||
|
||||
if not (PY2 and NOSE2):
|
||||
missing_tests.update([
|
||||
])
|
||||
|
||||
test_params = [
|
||||
(42, ),
|
||||
@@ -81,6 +83,31 @@ class TestParameterized(object):
|
||||
missing_tests.remove("test_instance_method(%r, bar=%r)" %(foo, bar))
|
||||
|
||||
|
||||
if not PYTEST:
|
||||
# py.test doesn't use xunit-style setup/teardown, so these tests don't apply
|
||||
class TestSetupTeardown(object):
|
||||
expect("generator", [
|
||||
"test_setup(setup 1)",
|
||||
"teardown_called(teardown 1)",
|
||||
"test_setup(setup 2)",
|
||||
"teardown_called(teardown 2)",
|
||||
])
|
||||
|
||||
stack = ["setup 1", "teardown 1", "setup 2", "teardown 2"]
|
||||
actual_order = "error: setup not called"
|
||||
|
||||
def setUp(self):
|
||||
self.actual_order = self.stack.pop(0)
|
||||
|
||||
def tearDown(self):
|
||||
missing_tests.remove("teardown_called(%s)" %(self.stack.pop(0), ))
|
||||
|
||||
@parameterized([(1, ), (2, )])
|
||||
def test_setup(self, count, *a):
|
||||
assert_equal(self.actual_order, "setup %s" %(count, ))
|
||||
missing_tests.remove("test_setup(%s)" %(self.actual_order, ))
|
||||
|
||||
|
||||
def custom_naming_func(custom_tag):
|
||||
def custom_naming_func(testcase_func, param_num, param):
|
||||
return testcase_func.__name__ + ('_%s_name_' % custom_tag) + str(param.args[0])
|
||||
@@ -185,7 +212,7 @@ class TestParameterizedExpandDocstring(TestCase):
|
||||
def test_warns_when_using_parameterized_with_TestCase():
|
||||
try:
|
||||
class TestTestCaseWarnsOnBadUseOfParameterized(TestCase):
|
||||
@parameterized([42])
|
||||
@parameterized([(42, )])
|
||||
def test_in_subclass_of_TestCase(self, foo):
|
||||
pass
|
||||
except Exception as e:
|
||||
@@ -193,6 +220,14 @@ def test_warns_when_using_parameterized_with_TestCase():
|
||||
else:
|
||||
raise AssertionError("Expected exception not raised")
|
||||
|
||||
def test_helpful_error_on_invalid_parameters():
|
||||
try:
|
||||
parameterized([1432141234243])(lambda: None)
|
||||
except Exception as e:
|
||||
assert_contains(str(e), "Parameters must be tuples")
|
||||
else:
|
||||
raise AssertionError("Expected exception not raised")
|
||||
|
||||
expect("generator", [
|
||||
"test_wrapped_iterable_input('foo')",
|
||||
])
|
||||
@@ -202,8 +237,7 @@ def test_wrapped_iterable_input(foo):
|
||||
|
||||
def test_helpful_error_on_non_iterable_input():
|
||||
try:
|
||||
for _ in parameterized(lambda: 42)(lambda: None)():
|
||||
pass
|
||||
parameterized(lambda: 42)(lambda: None)
|
||||
except Exception as e:
|
||||
assert_contains(str(e), "is not iterable")
|
||||
else:
|
||||
@@ -268,7 +302,8 @@ def test_parameterized_argument_value_pairs(func_params, p, expected):
|
||||
@parameterized([
|
||||
("abcd", "'abcd'"),
|
||||
("123456789", "'12...89'"),
|
||||
(123456789, "123...789") # number types do not have quotes, so we can repr more
|
||||
(123456789, "123...789"),
|
||||
(123456789, "12...89", 4),
|
||||
])
|
||||
def test_short_repr(input, expected, n=6):
|
||||
assert_equal(short_repr(input, n=n), expected)
|
||||
|
||||
Reference in New Issue
Block a user