diff options
author | Robert Collins <robertc@robertcollins.net> | 2010-10-12 19:57:23 +1300 |
---|---|---|
committer | Robert Collins <robertc@robertcollins.net> | 2010-10-12 19:57:23 +1300 |
commit | e1339ea008f571b21ad13c5d69926ed6d66bb290 (patch) | |
tree | b4b75f7b8ceb5626cbd9df75872f7bed1e7fded9 | |
parent | 553e0a70a190ab8fa81348532fb32b118ea6592f (diff) | |
parent | 977abd578ffa376be31e6382d4b0baab1c54ccef (diff) | |
download | testscenarios-git-e1339ea008f571b21ad13c5d69926ed6d66bb290.tar.gz |
Merge a tweaked version of Martins load_tests and multiply_scenarios patch.
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | README | 26 | ||||
-rw-r--r-- | lib/testscenarios/__init__.py | 9 | ||||
-rw-r--r-- | lib/testscenarios/scenarios.py | 53 | ||||
-rw-r--r-- | lib/testscenarios/tests/test_scenarios.py | 68 |
5 files changed, 158 insertions, 1 deletions
@@ -13,6 +13,9 @@ CHANGES: * Adjust the cloned tests ``shortDescription`` if one is present. (Ben Finney) +* Provide a load_tests implementation for easy use, and multiply_scenarios to + create the cross product of scenarios. (Martin Pool) + 0.1 ~~~ @@ -128,6 +128,15 @@ With ``load_tests``:: ... result.addTests(generate_scenarios(standard_tests)) ... return result +as a convenience, this is available in ``load_tests_apply_scenarios``, so a +module using scenario tests need only say :: + + >>> from testscenarios import load_tests_apply_scenarios as load_tests + +Python 2.7 and greater support a different calling convention for `load_tests`` +<https://bugs.launchpad.net/bzr/+bug/607412>. `load_tests_apply_scenarios` +copes with both. + With ``test_suite``:: >>> def test_suite(): @@ -254,3 +263,20 @@ Advice on Writing Scenarios If a parameterised test is because of a bug run without being parameterized, it should fail rather than running with defaults, because this can hide bugs. + + +Producing Scenarios +=================== + +The `multiply_scenarios` function produces the cross-product of the scenarios +passed in:: + + >>> from testscenarios.scenarios import multiply_scenarios + >>> + >>> scenarios = multiply_scenarios( + ... [('scenario1', dict(param1=1)), ('scenario2', dict(param1=2))], + ... [('scenario2', dict(param2=1))], + ... ) + >>> scenarios == [('scenario1,scenario2', {'param2': 1, 'param1': 1}), + ... ('scenario2,scenario2', {'param2': 1, 'param1': 2})] + True diff --git a/lib/testscenarios/__init__.py b/lib/testscenarios/__init__.py index d608f13..c501525 100644 --- a/lib/testscenarios/__init__.py +++ b/lib/testscenarios/__init__.py @@ -45,12 +45,19 @@ __all__ = [ 'apply_scenario', 'apply_scenarios', 'generate_scenarios', + 'load_tests_apply_scenarios', + 'multiply_scenarios', ] import unittest -from testscenarios.scenarios import apply_scenario, generate_scenarios +from testscenarios.scenarios import ( + apply_scenario, + generate_scenarios, + load_tests_apply_scenarios, + multiply_scenarios, + ) from testscenarios.testcase import TestWithScenarios diff --git a/lib/testscenarios/scenarios.py b/lib/testscenarios/scenarios.py index e531b2e..80847d6 100644 --- a/lib/testscenarios/scenarios.py +++ b/lib/testscenarios/scenarios.py @@ -2,6 +2,7 @@ # dependency injection ('scenarios') by tests. # # Copyright (c) 2009, Robert Collins <robertc@robertcollins.net> +# Copyright (c) 2010 Martin Pool <mbp@sourcefrog.net> # # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause # license at the users choice. A copy of both licenses are available in the @@ -18,8 +19,14 @@ __all__ = [ 'apply_scenario', 'apply_scenarios', 'generate_scenarios', + 'load_tests_apply_scenarios', + 'multiply_scenarios', ] +from itertools import ( + chain, + product, + ) import unittest from testtools.testcase import clone_test_with_new_id @@ -76,3 +83,49 @@ def generate_scenarios(test_or_suite): yield newtest else: yield test + + +def load_tests_apply_scenarios(*params): + """Adapter test runner load hooks to call generate_scenarios. + + If this is referenced by the `load_tests` attribute of a module, then + testloaders that implement this protocol will automatically arrange for + the scenarios to be expanded. This can be used instead of using + TestWithScenarios. + + Two different calling conventions for load_tests have been used, and this + function should support both. Python 2.7 passes (loader, standard_tests, + pattern), and bzr used (standard_tests, module, loader). + + :param loader: A TestLoader. + :param standard_test: The test objects found in this module before + multiplication. + """ + if getattr(params[0], 'suiteClass', None) is not None: + loader, standard_tests, pattern = params + else: + standard_tests, module, loader = params + result = loader.suiteClass() + result.addTests(generate_scenarios(standard_tests)) + return result + + +def multiply_scenarios(*scenarios): + """Multiply two or more iterables of scenarios. + + It is safe to pass scenario generators or iterators. + + :returns: A list of compound scenarios: the cross-product of all + scenarios, with the names concatenated and the parameters + merged together. + """ + result = [] + scenario_lists = map(list, scenarios) + for combination in product(*scenario_lists): + names, parameters = zip(*combination) + scenario_name = ','.join(names) + scenario_parameters = {} + for parameter in parameters: + scenario_parameters.update(parameter) + result.append((scenario_name, scenario_parameters)) + return result diff --git a/lib/testscenarios/tests/test_scenarios.py b/lib/testscenarios/tests/test_scenarios.py index 4c80150..063df51 100644 --- a/lib/testscenarios/tests/test_scenarios.py +++ b/lib/testscenarios/tests/test_scenarios.py @@ -2,6 +2,7 @@ # dependency injection ('scenarios') by tests. # # Copyright (c) 2009, Robert Collins <robertc@robertcollins.net> +# Copyright (c) 2010 Martin Pool <mbp@sourcefrog.net> # # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause # license at the users choice. A copy of both licenses are available in the @@ -21,6 +22,8 @@ from testscenarios.scenarios import ( apply_scenario, apply_scenarios, generate_scenarios, + load_tests_apply_scenarios, + multiply_scenarios, ) import testtools from testtools.tests.helpers import LoggingResult @@ -171,3 +174,68 @@ class TestApplyScenarios(testtools.TestCase): tests = list(apply_scenarios(ReferenceTest.scenarios, test)) self.assertEqual([('demo', {})], ReferenceTest.scenarios) self.assertEqual(ReferenceTest.scenarios, tests[0].scenarios) + + +class TestLoadTests(testtools.TestCase): + + class SampleTest(unittest.TestCase): + def test_nothing(self): + pass + scenarios = [ + ('a', {}), + ('b', {}), + ] + + def test_load_tests_apply_scenarios(self): + suite = load_tests_apply_scenarios( + unittest.TestLoader(), + [self.SampleTest('test_nothing')], + None) + result_tests = list(testtools.iterate_tests(suite)) + self.assertEquals( + 2, + len(result_tests), + result_tests) + + def test_load_tests_apply_scenarios_old_style(self): + """Call load_tests in the way used by bzr.""" + suite = load_tests_apply_scenarios( + [self.SampleTest('test_nothing')], + self.__class__.__module__, + unittest.TestLoader(), + ) + result_tests = list(testtools.iterate_tests(suite)) + self.assertEquals( + 2, + len(result_tests), + result_tests) + + +class TestMultiplyScenarios(testtools.TestCase): + + def test_multiply_scenarios(self): + def factory(name): + for i in 'ab': + yield i, {name: i} + scenarios = multiply_scenarios(factory('p'), factory('q')) + self.assertEqual([ + ('a,a', dict(p='a', q='a')), + ('a,b', dict(p='a', q='b')), + ('b,a', dict(p='b', q='a')), + ('b,b', dict(p='b', q='b')), + ], + scenarios) + + def test_multiply_many_scenarios(self): + def factory(name): + for i in 'abc': + yield i, {name: i} + scenarios = multiply_scenarios(factory('p'), factory('q'), + factory('r'), factory('t')) + self.assertEqual( + 3**4, + len(scenarios), + scenarios) + self.assertEqual( + 'a,a,a,a', + scenarios[0][0]) |