From a211805071b8400b1061066a8bd299a2c76206d5 Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Sat, 17 Aug 2024 12:15:14 -0400 Subject: [PATCH] Fix using parameterized_class with mixins Extend the fix for #73 to include inherited methods: copy all test methods from the original class into generated classes and set them to None in the original class. This ensures that the original class does not run by itself. Fixes #119 --- parameterized/parameterized.py | 26 +++++++++------- parameterized/test.py | 54 +++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/parameterized/parameterized.py b/parameterized/parameterized.py index 56dc535..a521eb6 100644 --- a/parameterized/parameterized.py +++ b/parameterized/parameterized.py @@ -687,23 +687,27 @@ class TestUserAccessLevel(TestCase): def decorator(base_class): test_class_module = sys.modules[base_class.__module__].__dict__ + # We need to leave the base class in place (see issue #73), but if we leave + # test methods in place, the test runner will try to pick them up and run + # them, which doesn't make sense, since no parameters will have been applied. + # Address this by copying all test methods into generated classes and setting + # them to None in the base class. + test_methods = { + method_name: getattr(base_class, method_name) + for method_name in dir(base_class) + if method_name.startswith("test") + } + for method_name in test_methods: + setattr(base_class, method_name, None) + + base_dict = {**base_class.__dict__, **test_methods} for idx, input_dict in enumerate(input_dicts): - test_class_dict = dict(base_class.__dict__) - test_class_dict.update(input_dict) + test_class_dict = {**base_dict, **input_dict} name = class_name_func(base_class, idx, input_dict) test_class_module[name] = type(name, (base_class, ), test_class_dict) - # We need to leave the base class in place (see issue #73), but if we - # leave the test_ methods in place, the test runner will try to pick - # them up and run them... which doesn't make sense, since no parameters - # will have been applied. - # Address this by iterating over the base class and remove all test - # methods. - for method_name in list(base_class.__dict__): - if method_name.startswith("test"): - delattr(base_class, method_name) return base_class return decorator diff --git a/parameterized/test.py b/parameterized/test.py index 6c71f79..c1cb228 100644 --- a/parameterized/test.py +++ b/parameterized/test.py @@ -35,7 +35,7 @@ def assert_raises_regexp_decorator(expected_exception, expected_regexp): def func_decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): - with self.assertRaisesRegexp(expected_exception, expected_regexp): + with self.assertRaisesRegex(expected_exception, expected_regexp): func(self, *args, **kwargs) return wrapper @@ -615,12 +615,28 @@ def test_method(self): )) +class FoobarTestCase(TestCase): + def setUp(self): + missing_tests.remove("%s:setUp(%r, %r)" %( + self.__class__.__name__, + self.foo, + self.bar, + )) + + def tearDown(self): + missing_tests.remove("%s:tearDown(%r, %r)" %( + self.__class__.__name__, + self.foo, + self.bar, + )) + + @parameterized_class([ {"foo": 42}, {"bar": "some stuff"}, {"bar": "other stuff", "name": "some name", "foo": 12}, ]) -class TestParameterizedClassDict(TestCase): +class TestParameterizedClassDict(FoobarTestCase): expect([ "TestParameterizedClassDict_0:setUp(42, 'empty')", "TestParameterizedClassDict_0:test_method(42, 'empty')", @@ -637,23 +653,30 @@ class TestParameterizedClassDict(TestCase): bar = 'empty' def setUp(self): - # Ensure that super() works (issue #73) + # Ensure that super() with arguments works (issue #73) super(TestParameterizedClassDict, self).setUp() - missing_tests.remove("%s:setUp(%r, %r)" %( - self.__class__.__name__, - self.foo, - self.bar, - )) def tearDown(self): - # Ensure that super() works (issue #73) - super(TestParameterizedClassDict, self).tearDown() - missing_tests.remove("%s:tearDown(%r, %r)" %( + # Ensure that super() without arguments works + super().tearDown() + + def test_method(self): + missing_tests.remove("%s:test_method(%r, %r)" %( self.__class__.__name__, self.foo, self.bar, )) + +class TestMixin: + def setUp(self): + # Ensure that super() with arguments works + super(TestMixin, self).setUp() + + def tearDown(self): + # Ensure that super() without arguments works + super().tearDown() + def test_method(self): missing_tests.remove("%s:test_method(%r, %r)" %( self.__class__.__name__, @@ -662,6 +685,15 @@ def test_method(self): )) +@parameterized_class([{"foo": 42, "bar": 21}]) +class TestParameterizedMixin(TestMixin, FoobarTestCase): + expect([ + "TestParameterizedMixin_0:setUp(42, 21)", + "TestParameterizedMixin_0:test_method(42, 21)", + "TestParameterizedMixin_0:tearDown(42, 21)", + ]) + + class TestUnicodeDocstring(object): @parameterized.expand([ 'value1',