33 """Adds support for parameterized tests to Python's unittest TestCase class.
35 A parameterized test is a method in a test case that is invoked with different
40 class AdditionExample(parameterized.TestCase):
41 @parameterized.parameters(
45 def testAddition(self, op1, op2, result):
46 self.assertEqual(result, op1 + op2)
49 Each invocation is a separate test case and properly isolated just
50 like a normal test method, with its own setUp/tearDown cycle. In the
51 example above, there are three separate testcases, one of which will
52 fail due to an assertion error (1 + 1 != 3).
54 Parameters for invididual test cases can be tuples (with positional parameters)
55 or dictionaries (with named parameters):
57 class AdditionExample(parameterized.TestCase):
58 @parameterized.parameters(
59 {'op1': 1, 'op2': 2, 'result': 3},
60 {'op1': 4, 'op2': 5, 'result': 9},
62 def testAddition(self, op1, op2, result):
63 self.assertEqual(result, op1 + op2)
65 If a parameterized test fails, the error message will show the
66 original test name (which is modified internally) and the arguments
67 for the specific invocation, which are part of the string returned by
68 the shortDescription() method on test cases.
70 The id method of the test, used internally by the unittest framework,
71 is also modified to show the arguments. To make sure that test names
72 stay the same across several invocations, object representations like
74 >>> class Foo(object):
77 '<__main__.Foo object at 0x23d8610>'
79 are turned into '<__main__.Foo>'. For even more descriptive names,
80 especially in test logs, you can use the named_parameters decorator. In
81 this case, only tuples are supported, and the first parameters has to
82 be a string (or an object that returns an apt name when converted via
85 class NamedExample(parameterized.TestCase):
86 @parameterized.named_parameters(
87 ('Normal', 'aa', 'aaa', True),
88 ('EmptyPrefix', '', 'abc', True),
89 ('BothEmpty', '', '', True))
90 def testStartsWith(self, prefix, string, result):
91 self.assertEqual(result, strings.startswith(prefix))
93 Named tests also have the benefit that they can be run individually
94 from the command line:
96 $ testmodule.py NamedExample.testStartsWithNormal
98 --------------------------------------------------------------------
103 Parameterized Classes
104 =====================
105 If invocation arguments are shared across test methods in a single
106 TestCase class, instead of decorating all test methods
107 individually, the class itself can be decorated:
109 @parameterized.parameters(
112 class ArithmeticTest(parameterized.TestCase):
113 def testAdd(self, arg1, arg2, result):
114 self.assertEqual(arg1 + arg2, result)
116 def testSubtract(self, arg2, arg2, result):
117 self.assertEqual(result - arg1, arg2)
119 Inputs from Iterables
120 =====================
121 If parameters should be shared across several test cases, or are dynamically
122 created from other sources, a single non-tuple iterable can be passed into
123 the decorator. This iterable will be used to obtain the test cases:
125 class AdditionExample(parameterized.TestCase):
126 @parameterized.parameters(
127 c.op1, c.op2, c.result for c in testcases
129 def testAddition(self, op1, op2, result):
130 self.assertEqual(result, op1 + op2)
133 Single-Argument Test Methods
134 ============================
135 If a test method takes only one argument, the single argument does not need to
136 be wrapped into a tuple:
138 class NegativeNumberExample(parameterized.TestCase):
139 @parameterized.parameters(
142 def testIsNegative(self, arg):
143 self.assertTrue(IsNegative(arg))
146 __author__ =
'tmarek@google.com (Torsten Marek)'
152 import unittest2
as unittest
161 import collections.abc
as collections_abc
164 import collections
as collections_abc
166 ADDR_RE = re.compile(
r'<([a-zA-Z0-9_\-\.]+) object at 0x[a-fA-F0-9]+>')
167 _SEPARATOR = uuid.uuid1().hex
168 _FIRST_ARG = object()
169 _ARGUMENT_REPR = object()
173 return ADDR_RE.sub(
r'<\1>', repr(obj))
179 return '%s.%s' % (cls.__module__, cls.__name__)
183 return (isinstance(obj, collections_abc.Iterable)
and not
184 isinstance(obj, six.string_types))
188 if isinstance(testcase_params, collections_abc.Mapping):
189 return ', '.join(
'%s=%s' % (argname,
_CleanRepr(value))
190 for argname, value
in testcase_params.items())
192 return ', '.join(
map(_CleanRepr, testcase_params))
198 """Callable and iterable class for producing new test cases."""
200 def __init__(self, test_method, testcases, naming_type):
201 """Returns concrete test functions for a test and a list of parameters.
203 The naming_type is used to determine the name of the concrete
204 functions as reported by the unittest framework. If naming_type is
205 _FIRST_ARG, the testcases must be tuples, and the first element must
206 have a string representation that is a valid Python identifier.
209 test_method: The decorated test method.
210 testcases: (list of tuple/dict) A list of parameter
211 tuples/dicts for individual test invocations.
212 naming_type: The test naming type, either _NAMED or _ARGUMENT_REPR.
219 raise RuntimeError(
'You appear to be running a parameterized test case '
220 'without having inherited from parameterized.'
221 'TestCase. This is bad because none of '
222 'your test cases are actually being run.')
228 def MakeBoundParamTest(testcase_params):
229 @functools.wraps(test_method)
230 def BoundParamTest(self):
231 if isinstance(testcase_params, collections_abc.Mapping):
232 test_method(self, **testcase_params)
234 test_method(self, *testcase_params)
236 test_method(self, testcase_params)
238 if naming_type
is _FIRST_ARG:
241 BoundParamTest.__x_use_name__ =
True
242 BoundParamTest.__name__ +=
str(testcase_params[0])
243 testcase_params = testcase_params[1:]
244 elif naming_type
is _ARGUMENT_REPR:
249 BoundParamTest.__x_extra_id__ =
'(%s)' % (
252 raise RuntimeError(
'%s is not a valid naming type.' % (naming_type,))
254 BoundParamTest.__doc__ =
'%s(%s)' % (
256 if test_method.__doc__:
257 BoundParamTest.__doc__ +=
'\n%s' % (test_method.__doc__,)
258 return BoundParamTest
259 return (MakeBoundParamTest(c)
for c
in self.
testcases)
263 """True iff testcases contains only a single non-tuple element."""
264 return len(testcases) == 1
and not isinstance(testcases[0], tuple)
268 assert not getattr(class_object,
'_id_suffix',
None), (
269 'Cannot add parameters to %s,'
270 ' which already has parameterized methods.' % (class_object,))
271 class_object._id_suffix = id_suffix = {}
274 for name, obj
in class_object.__dict__.copy().items():
275 if (name.startswith(unittest.TestLoader.testMethodPrefix)
276 and isinstance(obj, types.FunctionType)):
277 delattr(class_object, name)
280 methods, id_suffix, name,
282 for name, meth
in methods.items():
283 setattr(class_object, name, meth)
287 """Implementation of the parameterization decorators.
290 naming_type: The naming type.
291 testcases: Testcase parameters.
294 A function for modifying the decorated object.
297 if isinstance(obj, type):
300 list(testcases)
if not isinstance(testcases, collections_abc.Sequence)
309 'Single parameter argument must be a non-string iterable')
310 testcases = testcases[0]
316 """A decorator for creating parameterized tests.
318 See the module docstring for a usage example.
320 *testcases: Parameters for the decorated method, either a single
321 iterable, or a list of tuples/dicts/objects (for tests
322 with only one argument).
325 A test generator to be handled by TestGeneratorMetaclass.
331 """A decorator for creating parameterized tests.
333 See the module docstring for a usage example. The first element of
334 each parameter tuple should be a string and will be appended to the
335 name of the test method.
338 *testcases: Parameters for the decorated method, either a single
339 iterable, or a list of tuples.
342 A test generator to be handled by TestGeneratorMetaclass.
348 """Metaclass for test cases with test generators.
350 A test generator is an iterable in a testcase that produces callables. These
351 callables must be single-argument methods. These methods are injected into
352 the class namespace and the original iterable is removed. If the name of the
353 iterable conforms to the test pattern, the injected methods will be picked
354 up as tests by the unittest framework.
356 In general, it is supposed to be used in conjunction with the
357 parameters decorator.
361 dct[
'_id_suffix'] = id_suffix = {}
362 for name, obj
in dct.items():
363 if (name.startswith(unittest.TestLoader.testMethodPrefix)
and
369 return type.__new__(mcs, class_name, bases, dct)
373 """Adds individual test cases to a dictionary.
376 dct: The target dictionary.
377 id_suffix: The dictionary for mapping names to test IDs.
378 name: The original name of the test case.
379 iterator: The iterator generating the individual test cases.
381 for idx, func
in enumerate(iterator):
382 assert callable(func),
'Test generators must yield callables, got %r' % (
384 if getattr(func,
'__x_use_name__',
False):
385 new_name = func.__name__
387 new_name =
'%s%s%d' % (name, _SEPARATOR, idx)
388 assert new_name
not in dct, (
389 'Name of parameterized test case "%s" not unique' % (new_name,))
391 id_suffix[new_name] =
getattr(func,
'__x_extra_id__',
'')
395 """Base class for test cases using the parameters decorator."""
396 __metaclass__ = TestGeneratorMetaclass
399 return self._testMethodName.split(_SEPARATOR)[0]
405 """Returns the descriptive ID of the test.
407 This is used internally by the unittesting framework to get a name
408 for the test to be used in reports.
413 return '%s.%s%s' % (
_StrClass(self.__class__),
415 self._id_suffix.get(self._testMethodName,
''))
419 """Returns a new base class with a cooperative metaclass base.
421 This enables the TestCase to be used in combination
422 with other base classes that have custom metaclasses, such as
425 Only works with metaclasses that do not override type.__new__.
432 from google3.testing.pybase import parameterized
434 class ExampleTest(parameterized.CoopTestCase(mox.MoxTestBase)):
438 other_base_class: (class) A test case base class.
445 (other_base_class.__metaclass__,
446 TestGeneratorMetaclass), {})
449 (other_base_class, TestCase), {})