aboutsummaryrefslogtreecommitdiff
path: root/absl/testing/parameterized.py
diff options
context:
space:
mode:
Diffstat (limited to 'absl/testing/parameterized.py')
-rw-r--r--absl/testing/parameterized.py66
1 files changed, 57 insertions, 9 deletions
diff --git a/absl/testing/parameterized.py b/absl/testing/parameterized.py
index 894cf6c..570dbb6 100644
--- a/absl/testing/parameterized.py
+++ b/absl/testing/parameterized.py
@@ -170,6 +170,21 @@ test combinations:
def testModuloResult(self, num, modulo, expected):
self.assertEqual(expected, num % modulo)
+This results in 6 test cases being created - one for each combination of the
+parameters. It is also possible to supply sequences of keyword argument dicts
+as elements of the cartesian product:
+
+ @parameterized.product(
+ (dict(num=5, modulo=3, expected=2),
+ dict(num=7, modulo=4, expected=3)),
+ dtype=(int, float)
+ )
+ def testModuloResult(self, num, modulo, expected):
+ self.assertEqual(expected, dtype(num) % modulo)
+
+This results in 4 test cases being created - for each of the two sets of test
+data (supplied as kwarg dicts) and for each of the two data types (supplied as
+a named parameter). Multiple keyword argument dicts may be supplied if required.
Async Support
===============================
@@ -464,15 +479,19 @@ def named_parameters(*testcases):
return _parameter_decorator(_NAMED, testcases)
-def product(**testgrid):
+def product(*kwargs_seqs, **testgrid):
"""A decorator for running tests over cartesian product of parameters values.
See the module docstring for a usage example. The test will be run for every
possible combination of the parameters.
Args:
- **testgrid: A mapping of parameter names and their possible values.
- Possible values should given as either a list or a tuple.
+ *kwargs_seqs: Each positional parameter is a sequence of keyword arg dicts;
+ every test case generated will include exactly one kwargs dict from each
+ positional parameter; these will then be merged to form an overall list
+ of arguments for the test case.
+ **testgrid: A mapping of parameter names and their possible values. Possible
+ values should given as either a list or a tuple.
Raises:
NoTestsError: Raised when the decorator generates no tests.
@@ -482,24 +501,53 @@ def product(**testgrid):
"""
for name, values in testgrid.items():
- assert isinstance(values, list) or isinstance(values, tuple), (
+ assert isinstance(values, (list, tuple)), (
'Values of {} must be given as list or tuple, found {}'.format(
name, type(values)))
+ prior_arg_names = set()
+ for kwargs_seq in kwargs_seqs:
+ assert ((isinstance(kwargs_seq, (list, tuple))) and
+ all(isinstance(kwargs, dict) for kwargs in kwargs_seq)), (
+ 'Positional parameters must be a sequence of keyword arg'
+ 'dicts, found {}'
+ .format(kwargs_seq))
+ if kwargs_seq:
+ arg_names = set(kwargs_seq[0])
+ assert all(set(kwargs) == arg_names for kwargs in kwargs_seq), (
+ 'Keyword argument dicts within a single parameter must all have the '
+ 'same keys, found {}'.format(kwargs_seq))
+ assert not (arg_names & prior_arg_names), (
+ 'Keyword argument dict sequences must all have distinct argument '
+ 'names, found duplicate(s) {}'
+ .format(sorted(arg_names & prior_arg_names)))
+ prior_arg_names |= arg_names
+
+ assert not (prior_arg_names & set(testgrid)), (
+ 'Arguments supplied in kwargs dicts in positional parameters must not '
+ 'overlap with arguments supplied as named parameters; found duplicate '
+ 'argument(s) {}'.format(sorted(prior_arg_names & set(testgrid))))
+
+ # Convert testgrid into a sequence of sequences of kwargs dicts and combine
+ # with the positional parameters.
+ # So foo=[1,2], bar=[3,4] --> [[{foo: 1}, {foo: 2}], [{bar: 3, bar: 4}]]
+ testgrid = (tuple({k: v} for v in vs) for k, vs in testgrid.items())
+ testgrid = tuple(kwargs_seqs) + tuple(testgrid)
+
# Create all possible combinations of parameters as a cartesian product
# of parameter values.
testcases = [
- dict(zip(testgrid.keys(), product))
- for product in itertools.product(*testgrid.values())
+ dict(itertools.chain.from_iterable(case.items()
+ for case in cases))
+ for cases in itertools.product(*testgrid)
]
-
return _parameter_decorator(_ARGUMENT_REPR, testcases)
class TestGeneratorMetaclass(type):
"""Metaclass for adding tests generated by parameterized decorators."""
- def __new__(mcs, class_name, bases, dct):
+ def __new__(cls, class_name, bases, dct):
# NOTE: _test_params_repr is private to parameterized.TestCase and it's
# metaclass; do not use it outside of those classes.
test_params_reprs = dct.setdefault('_test_params_reprs', {})
@@ -544,7 +592,7 @@ class TestGeneratorMetaclass(type):
# That's why it should only inherit it if it does not exist.
test_params_reprs.setdefault(test_method, test_method_id)
- return type.__new__(mcs, class_name, bases, dct)
+ return type.__new__(cls, class_name, bases, dct)
def _update_class_dict_for_param_test_case(