Clean up and document classname_func
diff --git a/README.rst b/README.rst
index f004203..186535f 100644
--- a/README.rst
+++ b/README.rst
@@ -504,41 +504,75 @@
.. code:: python
- from yourapp.models import User
- from parameterized import parameterized_class
+ from yourapp.models import User
+ from parameterized import parameterized_class
- @parameterized_class(("username", "access_level", "expected_status_code"), [
- ("user_1", 1, 200),
- ("user_2", 2, 404)
- ])
- class TestUserAccessLevel(TestCase):
- def setUp(self):
- self.client.force_login(User.objects.get(username=self.username)[0])
+ @parameterized_class([
+ { "username": "user_1", "access_level": 1 },
+ { "username": "user_2", "access_level": 2, "expected_status_code": 404 },
+ ])
+ class TestUserAccessLevel(TestCase):
+ expected_status_code = 200
- def test_url_a(self):
- response = self.client.get("/url")
- self.assertEqual(response.status_code, self.expected_status_code)
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
- def tearDown(self):
- self.client.logout()
+ def test_url_a(self):
+ response = self.client.get('/url')
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
- @parameterized_class([
- { "username": "user_1", "access_level": 1 },
- { "username": "user_2", "access_level": 2, "expected_status_code": 404 },
- ])
- class TestUserAccessLevel(TestCase):
- expected_status_code = 200
+ @parameterized_class(("username", "access_level", "expected_status_code"), [
+ ("user_1", 1, 200),
+ ("user_2", 2, 404)
+ ])
+ class TestUserAccessLevel(TestCase):
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
- def setUp(self):
- self.client.force_login(User.objects.get(username=self.username)[0])
+ def test_url_a(self):
+ response = self.client.get("/url")
+ self.assertEqual(response.status_code, self.expected_status_code)
- def test_url_a(self):
- response = self.client.get('/url')
- self.assertEqual(response.status_code, self.expected_status_code)
+ def tearDown(self):
+ self.client.logout()
- def tearDown(self):
- self.client.logout()
+
+The ``@parameterized_class`` decorator accepts a ``classname_func`` argument,
+which controls the name of the parameterized classes generated by
+``@parameterized_class``:
+
+.. code:: python
+
+ from parameterized import parameterized, parameterized_class
+
+ def get_classname(cls, num, params_dict):
+ # By default the generated class named includes either the "name"
+ # parameter (if present), or the first string value. This example shows
+ # multiple parameters being included in the generated class name:
+ return "%s_%s_%s%s" %(
+ cls.__name__,
+ num,
+ parameterized.to_safe_name(params_dict['a']),
+ parameterized.to_safe_name(params_dict['b']),
+ )
+
+ @parameterized_class([
+ { "a": "hello", "b": " world!", "expected": "hello world!" },
+ { "a": "say ", "b": " cheese :)", "expected": "say cheese :)" },
+ ], classname_func=get_classname)
+ class TestConcatenation(TestCase):
+ def test_concat(self):
+ self.assertEqual(self.a + self.b, self.expected)
+
+::
+
+ $ nosetests -v test_math.py
+ test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok
+ test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok
diff --git a/parameterized/parameterized.py b/parameterized/parameterized.py
index 090f508..e8e69ab 100644
--- a/parameterized/parameterized.py
+++ b/parameterized/parameterized.py
@@ -62,9 +62,6 @@
return CompatArgSpec(*args[:4])
-_param = namedtuple("param", "args kwargs")
-
-
def skip_on_empty_helper(*a, **kw):
raise SkipTest("parameterized input is empty")
@@ -91,6 +88,8 @@
func.patchings[:] = []
+_param = namedtuple("param", "args kwargs")
+
class param(_param):
""" Represents a single parameter to a test case.
@@ -590,7 +589,7 @@
test_class_dict = dict(base_class.__dict__)
test_class_dict.update(input_dict)
- name = classname_func(base_class, idx, input_dicts)
+ name = classname_func(base_class, idx, input_dict)
test_class_module[name] = type(name, (base_class, ), test_class_dict)
@@ -608,19 +607,24 @@
return decorator
-def default_classname_func(cls, num, p):
- name_suffix = p and p[num]
- if isinstance(name_suffix, (list, tuple)) and len(p) > 0:
- name_suffix = name_suffix[0]
- name_suffix = (
- "_%s" %(name_suffix, ) if isinstance(name_suffix, string_types) else
- ""
- )
+def get_classname_suffix(params_dict):
+ if "name" in params_dict:
+ return parameterized.to_safe_name(params_dict["name"])
- name = "%s_%s%s" %(
+ params_vals = (
+ params_dict.values() if PY3 else
+ (v for (_, v) in sorted(params_dict.items()))
+ )
+ return parameterized.to_safe_name(next((
+ v for v in params_vals
+ if isinstance(v, string_types)
+ ), ""))
+
+
+def default_classname_func(cls, num, params_dict):
+ suffix = get_classname_suffix(params_dict)
+ return "%s_%s%s" %(
cls.__name__,
num,
- name_suffix,
+ suffix and "_" + suffix,
)
-
- return name
diff --git a/parameterized/test.py b/parameterized/test.py
index c263ded..5911892 100644
--- a/parameterized/test.py
+++ b/parameterized/test.py
@@ -447,17 +447,14 @@
@parameterized_class(("a", "b", "c"), [
("foo", 1, 2),
- ("bar", 3, 0),
(0, 1, 2),
])
class TestParameterizedClass(TestCase):
expect([
- "TestParameterizedClass_0:test_method_a('foo', 1, 2)",
- "TestParameterizedClass_0:test_method_b('foo', 1, 2)",
- "TestParameterizedClass_1:test_method_a('bar', 3, 0)",
- "TestParameterizedClass_1:test_method_b('bar', 3, 0)",
- "TestParameterizedClass_2:test_method_a(0, 1, 2)",
- "TestParameterizedClass_2:test_method_b(0, 1, 2)",
+ "TestParameterizedClass_0_foo:test_method_a('foo', 1, 2)",
+ "TestParameterizedClass_0_foo:test_method_b('foo', 1, 2)",
+ "TestParameterizedClass_1:test_method_a(0, 1, 2)",
+ "TestParameterizedClass_1:test_method_b(0, 1, 2)",
])
def _assertions(self, test_name):
@@ -478,67 +475,43 @@
self._assertions("test_method_b")
-def custom_cls_naming_func(clsname, idx, attrs):
- """
- Custom class naming function for the form of parameterized_class that
- takes a tuple of attributes, then a list of tuples of values
- :param clsname: The original class that the decorator specialises
- :param idx: the test index (starts at 0)
- :param attrs: A list of dicts of attribute values
- :return:
- """
- return "%s_%s_%s_%s" % (clsname.__name__, str(attrs[idx]['a']), str(attrs[idx]['b']), str(attrs[idx]['c']))
-
-
-@parameterized_class(("a", "b", "c"), [
- ("foo", 1, 2),
- ("bar", 3, 0),
- (0, 1, 2),
-], classname_func=custom_cls_naming_func)
+@parameterized_class(("a", ), [
+ (1, ),
+ (2, ),
+], classname_func=lambda cls, idx, attrs: "%s_custom_func_%s" %(cls.__name__, attrs["a"]))
class TestNamedParameterizedClass(TestCase):
expect([
- "TestNamedParameterizedClass_foo_1_2:test_method_a('foo', 1, 2)",
- "TestNamedParameterizedClass_foo_1_2:test_method_b('foo', 1, 2)",
- "TestNamedParameterizedClass_bar_3_0:test_method_a('bar', 3, 0)",
- "TestNamedParameterizedClass_bar_3_0:test_method_b('bar', 3, 0)",
- "TestNamedParameterizedClass_0_1_2:test_method_a(0, 1, 2)",
- "TestNamedParameterizedClass_0_1_2:test_method_b(0, 1, 2)",
+ "TestNamedParameterizedClass_custom_func_1:test_method(1)",
+ "TestNamedParameterizedClass_custom_func_2:test_method(2)",
])
- def _assertions(self, test_name):
- assert hasattr(self, "a")
- assert_equal(self.b + self.c, 3)
- missing_tests.remove("%s:%s(%r, %r, %r)" %(
+ def test_method(self):
+ missing_tests.remove("%s:test_method(%r)" %(
self.__class__.__name__,
- test_name,
self.a,
- self.b,
- self.c,
))
- def test_method_a(self):
- self._assertions("test_method_a")
-
- def test_method_b(self):
- self._assertions("test_method_b")
-
@parameterized_class([
- {"foo": 1},
- {"bar": 1},
+ {"foo": 42},
+ {"bar": "some stuff"},
+ {"bar": "other stuff", "name": "some name", "foo": 12},
])
class TestParameterizedClassDict(TestCase):
expect([
- "TestParameterizedClassDict_0:setUp(1, 0)",
- "TestParameterizedClassDict_0:tearDown(1, 0)",
- "TestParameterizedClassDict_0:test_method(1, 0)",
- "TestParameterizedClassDict_1:test_method(0, 1)",
- "TestParameterizedClassDict_1:setUp(0, 1)",
- "TestParameterizedClassDict_1:tearDown(0, 1)",
+ "TestParameterizedClassDict_0:setUp(42, 'empty')",
+ "TestParameterizedClassDict_0:test_method(42, 'empty')",
+ "TestParameterizedClassDict_0:tearDown(42, 'empty')",
+ "TestParameterizedClassDict_1_some_stuff:setUp(0, 'some stuff')",
+ "TestParameterizedClassDict_1_some_stuff:test_method(0, 'some stuff')",
+ "TestParameterizedClassDict_1_some_stuff:tearDown(0, 'some stuff')",
+ "TestParameterizedClassDict_2_some_name:setUp(12, 'other stuff')",
+ "TestParameterizedClassDict_2_some_name:test_method(12, 'other stuff')",
+ "TestParameterizedClassDict_2_some_name:tearDown(12, 'other stuff')",
])
foo = 0
- bar = 0
+ bar = 'empty'
def setUp(self):
# Ensure that super() works (issue #73)
@@ -564,37 +537,3 @@
self.foo,
self.bar,
))
-
-
-def custom_cls_naming_dict_func(clsname, idx, attrs):
- """
- Custom class naming function for the form of parameterized_class that
- takes a list of dictionaries containing attributes to override.
- :param clsname: The original class that the decorator specialises
- :param idx: the test index (starts at 0)
- :param attrs: A list of dicts of attribute values
- :return:
- """
- return "%s_%s_%s" % (clsname.__name__, str(attrs[idx].get('foo', 0)), str(attrs[idx].get('bar', 0)))
-
-
-@parameterized_class([
- {"foo": 1},
- {"bar": 1},
-], classname_func=custom_cls_naming_dict_func
-)
-class TestNamedParameterizedClassDict(TestCase):
- expect([
- "TestNamedParameterizedClassDict_1_0:test_method(1, 0)",
- "TestNamedParameterizedClassDict_0_1:test_method(0, 1)",
- ])
-
- foo = 0
- bar = 0
-
- def test_method(self):
- missing_tests.remove("%s:test_method(%r, %r)" %(
- self.__class__.__name__,
- self.foo,
- self.bar,
- ))