_parameterized.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 #
3 # Protocol Buffers - Google's data interchange format
4 # Copyright 2008 Google Inc. All rights reserved.
5 # https://developers.google.com/protocol-buffers/
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following disclaimer
15 # in the documentation and/or other materials provided with the
16 # distribution.
17 # * Neither the name of Google Inc. nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 
33 """Adds support for parameterized tests to Python's unittest TestCase class.
34 
35 A parameterized test is a method in a test case that is invoked with different
36 argument tuples.
37 
38 A simple example:
39 
40  class AdditionExample(parameterized.TestCase):
41  @parameterized.parameters(
42  (1, 2, 3),
43  (4, 5, 9),
44  (1, 1, 3))
45  def testAddition(self, op1, op2, result):
46  self.assertEqual(result, op1 + op2)
47 
48 
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).
53 
54 Parameters for invididual test cases can be tuples (with positional parameters)
55 or dictionaries (with named parameters):
56 
57  class AdditionExample(parameterized.TestCase):
58  @parameterized.parameters(
59  {'op1': 1, 'op2': 2, 'result': 3},
60  {'op1': 4, 'op2': 5, 'result': 9},
61  )
62  def testAddition(self, op1, op2, result):
63  self.assertEqual(result, op1 + op2)
64 
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.
69 
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
73 
74  >>> class Foo(object):
75  ... pass
76  >>> repr(Foo())
77  '<__main__.Foo object at 0x23d8610>'
78 
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
83 str()):
84 
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))
92 
93 Named tests also have the benefit that they can be run individually
94 from the command line:
95 
96  $ testmodule.py NamedExample.testStartsWithNormal
97  .
98  --------------------------------------------------------------------
99  Ran 1 test in 0.000s
100 
101  OK
102 
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:
108 
109  @parameterized.parameters(
110  (1, 2, 3)
111  (4, 5, 9))
112  class ArithmeticTest(parameterized.TestCase):
113  def testAdd(self, arg1, arg2, result):
114  self.assertEqual(arg1 + arg2, result)
115 
116  def testSubtract(self, arg2, arg2, result):
117  self.assertEqual(result - arg1, arg2)
118 
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:
124 
125  class AdditionExample(parameterized.TestCase):
126  @parameterized.parameters(
127  c.op1, c.op2, c.result for c in testcases
128  )
129  def testAddition(self, op1, op2, result):
130  self.assertEqual(result, op1 + op2)
131 
132 
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:
137 
138  class NegativeNumberExample(parameterized.TestCase):
139  @parameterized.parameters(
140  -1, -3, -4, -5
141  )
142  def testIsNegative(self, arg):
143  self.assertTrue(IsNegative(arg))
144 """
145 
146 __author__ = 'tmarek@google.com (Torsten Marek)'
147 
148 import functools
149 import re
150 import types
151 try:
152  import unittest2 as unittest
153 except ImportError:
154  import unittest
155 import uuid
156 
157 import six
158 
159 try:
160  # Since python 3
161  import collections.abc as collections_abc
162 except ImportError:
163  # Won't work after python 3.8
164  import collections as collections_abc
165 
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()
170 
171 
172 def _CleanRepr(obj):
173  return ADDR_RE.sub(r'<\1>', repr(obj))
174 
175 
176 # Helper function formerly from the unittest module, removed from it in
177 # Python 2.7.
178 def _StrClass(cls):
179  return '%s.%s' % (cls.__module__, cls.__name__)
180 
181 
183  return (isinstance(obj, collections_abc.Iterable) and not
184  isinstance(obj, six.string_types))
185 
186 
187 def _FormatParameterList(testcase_params):
188  if isinstance(testcase_params, collections_abc.Mapping):
189  return ', '.join('%s=%s' % (argname, _CleanRepr(value))
190  for argname, value in testcase_params.items())
191  elif _NonStringIterable(testcase_params):
192  return ', '.join(map(_CleanRepr, testcase_params))
193  else:
194  return _FormatParameterList((testcase_params,))
195 
196 
198  """Callable and iterable class for producing new test cases."""
199 
200  def __init__(self, test_method, testcases, naming_type):
201  """Returns concrete test functions for a test and a list of parameters.
202 
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.
207 
208  Args:
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.
213  """
214  self._test_method = test_method
215  self.testcases = testcases
216  self._naming_type = naming_type
217 
218  def __call__(self, *args, **kwargs):
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.')
223 
224  def __iter__(self):
225  test_method = self._test_method
226  naming_type = self._naming_type
227 
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)
233  elif _NonStringIterable(testcase_params):
234  test_method(self, *testcase_params)
235  else:
236  test_method(self, testcase_params)
237 
238  if naming_type is _FIRST_ARG:
239  # Signal the metaclass that the name of the test function is unique
240  # and descriptive.
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:
245  # __x_extra_id__ is used to pass naming information to the __new__
246  # method of TestGeneratorMetaclass.
247  # The metaclass will make sure to create a unique, but nondescriptive
248  # name for this test.
249  BoundParamTest.__x_extra_id__ = '(%s)' % (
250  _FormatParameterList(testcase_params),)
251  else:
252  raise RuntimeError('%s is not a valid naming type.' % (naming_type,))
253 
254  BoundParamTest.__doc__ = '%s(%s)' % (
255  BoundParamTest.__name__, _FormatParameterList(testcase_params))
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)
260 
261 
262 def _IsSingletonList(testcases):
263  """True iff testcases contains only a single non-tuple element."""
264  return len(testcases) == 1 and not isinstance(testcases[0], tuple)
265 
266 
267 def _ModifyClass(class_object, testcases, naming_type):
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 = {}
272  # We change the size of __dict__ while we iterate over it,
273  # which Python 3.x will complain about, so use copy().
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)
278  methods = {}
280  methods, id_suffix, name,
281  _ParameterizedTestIter(obj, testcases, naming_type))
282  for name, meth in methods.items():
283  setattr(class_object, name, meth)
284 
285 
286 def _ParameterDecorator(naming_type, testcases):
287  """Implementation of the parameterization decorators.
288 
289  Args:
290  naming_type: The naming type.
291  testcases: Testcase parameters.
292 
293  Returns:
294  A function for modifying the decorated object.
295  """
296  def _Apply(obj):
297  if isinstance(obj, type):
298  _ModifyClass(
299  obj,
300  list(testcases) if not isinstance(testcases, collections_abc.Sequence)
301  else testcases,
302  naming_type)
303  return obj
304  else:
305  return _ParameterizedTestIter(obj, testcases, naming_type)
306 
307  if _IsSingletonList(testcases):
308  assert _NonStringIterable(testcases[0]), (
309  'Single parameter argument must be a non-string iterable')
310  testcases = testcases[0]
311 
312  return _Apply
313 
314 
315 def parameters(*testcases): # pylint: disable=invalid-name
316  """A decorator for creating parameterized tests.
317 
318  See the module docstring for a usage example.
319  Args:
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).
323 
324  Returns:
325  A test generator to be handled by TestGeneratorMetaclass.
326  """
327  return _ParameterDecorator(_ARGUMENT_REPR, testcases)
328 
329 
330 def named_parameters(*testcases): # pylint: disable=invalid-name
331  """A decorator for creating parameterized tests.
332 
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.
336 
337  Args:
338  *testcases: Parameters for the decorated method, either a single
339  iterable, or a list of tuples.
340 
341  Returns:
342  A test generator to be handled by TestGeneratorMetaclass.
343  """
344  return _ParameterDecorator(_FIRST_ARG, testcases)
345 
346 
348  """Metaclass for test cases with test generators.
349 
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.
355 
356  In general, it is supposed to be used in conjunction with the
357  parameters decorator.
358  """
359 
360  def __new__(mcs, class_name, bases, dct):
361  dct['_id_suffix'] = id_suffix = {}
362  for name, obj in dct.items():
363  if (name.startswith(unittest.TestLoader.testMethodPrefix) and
364  _NonStringIterable(obj)):
365  iterator = iter(obj)
366  dct.pop(name)
367  _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator)
368 
369  return type.__new__(mcs, class_name, bases, dct)
370 
371 
372 def _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator):
373  """Adds individual test cases to a dictionary.
374 
375  Args:
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.
380  """
381  for idx, func in enumerate(iterator):
382  assert callable(func), 'Test generators must yield callables, got %r' % (
383  func,)
384  if getattr(func, '__x_use_name__', False):
385  new_name = func.__name__
386  else:
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,))
390  dct[new_name] = func
391  id_suffix[new_name] = getattr(func, '__x_extra_id__', '')
392 
393 
394 class TestCase(unittest.TestCase):
395  """Base class for test cases using the parameters decorator."""
396  __metaclass__ = TestGeneratorMetaclass
397 
398  def _OriginalName(self):
399  return self._testMethodName.split(_SEPARATOR)[0]
400 
401  def __str__(self):
402  return '%s (%s)' % (self._OriginalName(), _StrClass(self.__class__))
403 
404  def id(self): # pylint: disable=invalid-name
405  """Returns the descriptive ID of the test.
406 
407  This is used internally by the unittesting framework to get a name
408  for the test to be used in reports.
409 
410  Returns:
411  The test id.
412  """
413  return '%s.%s%s' % (_StrClass(self.__class__),
414  self._OriginalName(),
415  self._id_suffix.get(self._testMethodName, ''))
416 
417 
418 def CoopTestCase(other_base_class):
419  """Returns a new base class with a cooperative metaclass base.
420 
421  This enables the TestCase to be used in combination
422  with other base classes that have custom metaclasses, such as
423  mox.MoxTestBase.
424 
425  Only works with metaclasses that do not override type.__new__.
426 
427  Example:
428 
429  import google3
430  import mox
431 
432  from google3.testing.pybase import parameterized
433 
434  class ExampleTest(parameterized.CoopTestCase(mox.MoxTestBase)):
435  ...
436 
437  Args:
438  other_base_class: (class) A test case base class.
439 
440  Returns:
441  A new class object.
442  """
443  metaclass = type(
444  'CoopMetaclass',
445  (other_base_class.__metaclass__,
446  TestGeneratorMetaclass), {})
447  return metaclass(
448  'CoopTestCase',
449  (other_base_class, TestCase), {})
google::protobuf.internal._parameterized._ParameterizedTestIter.testcases
testcases
Definition: _parameterized.py:215
google::protobuf.internal._parameterized._ParameterizedTestIter.__init__
def __init__(self, test_method, testcases, naming_type)
Definition: _parameterized.py:200
getattr
static uint64_t getattr(const tarjan *t, const upb_refcounted *r)
Definition: ruby/ext/google/protobuf_c/upb.c:5868
google::protobuf.internal._parameterized.TestGeneratorMetaclass
Definition: _parameterized.py:347
google::protobuf.internal._parameterized.parameters
def parameters(*testcases)
Definition: _parameterized.py:315
google::protobuf.internal._parameterized._ParameterizedTestIter.__iter__
def __iter__(self)
Definition: _parameterized.py:224
google::protobuf.internal._parameterized._ParameterDecorator
def _ParameterDecorator(naming_type, testcases)
Definition: _parameterized.py:286
google::protobuf.internal._parameterized._ParameterizedTestIter
Definition: _parameterized.py:197
google::protobuf.internal._parameterized.TestCase
Definition: _parameterized.py:394
setattr
static void setattr(tarjan *t, const upb_refcounted *r, uint64_t attr)
Definition: ruby/ext/google/protobuf_c/upb.c:5875
map
zval * map
Definition: php/ext/google/protobuf/encode_decode.c:473
google::protobuf.internal._parameterized._ParameterizedTestIter._test_method
_test_method
Definition: _parameterized.py:214
google::protobuf.internal._parameterized._NonStringIterable
def _NonStringIterable(obj)
Definition: _parameterized.py:182
google::protobuf.internal._parameterized._ModifyClass
def _ModifyClass(class_object, testcases, naming_type)
Definition: _parameterized.py:267
google::protobuf.internal._parameterized.CoopTestCase
def CoopTestCase(other_base_class)
Definition: _parameterized.py:418
google::protobuf.internal._parameterized._StrClass
def _StrClass(cls)
Definition: _parameterized.py:178
google::protobuf.internal._parameterized.TestCase.__str__
def __str__(self)
Definition: _parameterized.py:401
google::protobuf.internal._parameterized._CleanRepr
def _CleanRepr(obj)
Definition: _parameterized.py:172
update_failure_list.str
str
Definition: update_failure_list.py:41
google::protobuf.internal._parameterized._ParameterizedTestIter._naming_type
_naming_type
Definition: _parameterized.py:216
google::protobuf.internal._parameterized.TestCase.id
def id(self)
Definition: _parameterized.py:404
len
int len
Definition: php/ext/google/protobuf/map.c:206
google::protobuf.internal._parameterized._IsSingletonList
def _IsSingletonList(testcases)
Definition: _parameterized.py:262
google::protobuf.internal._parameterized.named_parameters
def named_parameters(*testcases)
Definition: _parameterized.py:330
google::protobuf.internal._parameterized.TestCase._OriginalName
def _OriginalName(self)
Definition: _parameterized.py:398
google::protobuf.internal._parameterized._ParameterizedTestIter.__call__
def __call__(self, *args, **kwargs)
Definition: _parameterized.py:218
google::protobuf.internal._parameterized._FormatParameterList
def _FormatParameterList(testcase_params)
Definition: _parameterized.py:187
google::protobuf.internal._parameterized._UpdateClassDictForParamTestCase
def _UpdateClassDictForParamTestCase(dct, id_suffix, name, iterator)
Definition: _parameterized.py:372
google::protobuf.internal._parameterized.TestGeneratorMetaclass.__new__
def __new__(mcs, class_name, bases, dct)
Definition: _parameterized.py:360


libaditof
Author(s):
autogenerated on Wed May 21 2025 02:06:47