Merge lp:~mbp/testscenarios/658044-multiply into lp:~testtools-committers/testscenarios/trunk

Proposed by Martin Pool
Status: Merged
Merged at revision: 18
Proposed branch: lp:~mbp/testscenarios/658044-multiply
Merge into: lp:~testtools-committers/testscenarios/trunk
Diff against target: 209 lines (+149/-0)
3 files modified
README (+26/-0)
lib/testscenarios/scenarios.py (+53/-0)
lib/testscenarios/tests/test_scenarios.py (+70/-0)
To merge this branch: bzr merge lp:~mbp/testscenarios/658044-multiply
Reviewer Review Type Date Requested Status
Robert Collins Approve
Review via email: mp+38180@code.launchpad.net

Description of the change

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

tsk, naughty poolie - tests fail.

Also README is a doctest, so I'll just update, fix and merge.

review: Approve
Revision history for this message
Martin Pool (mbp) wrote :

Thanks.

Revision history for this message
Robert Collins (lifeless) wrote :

Also python works quite well with nouns as variable names rather than single letters :P

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README'
2--- README 2009-12-19 03:09:14 +0000
3+++ README 2010-10-12 05:03:45 +0000
4@@ -128,6 +128,19 @@
5 ... result.addTests(generate_scenarios(standard_tests))
6 ... return result
7
8+as a convenience, this is available in ``load_tests_apply_scenarios``, so a
9+module using scenario tests need only say ::
10+
11+ from testscenarios import load_tests_apply_scenarios
12+
13+ load_tests = load_tests_apply_scenarios
14+
15+It's suggested for clarity this be done near the top of the file.
16+
17+Python 2.7 and greater support a different calling convention for `load_tests``
18+<https://bugs.launchpad.net/bzr/+bug/607412>. `load_tests_apply_scenarios`
19+copes with both.
20+
21 With ``test_suite``::
22
23 >>> def test_suite():
24@@ -254,3 +267,16 @@
25
26 If a parameterised test is because of a bug run without being parameterized,
27 it should fail rather than running with defaults, because this can hide bugs.
28+
29+
30+Producing Scenarios
31+===================
32+
33+The `multiply_scenarios` function produces the cross-product of the scenarios
34+passed in::
35+
36+ scenarios = multiply_scenarios(
37+ vary_by_http_client(),
38+ vary_by_http_protocol_version(),
39+ )
40+
41
42=== modified file 'lib/testscenarios/scenarios.py'
43--- lib/testscenarios/scenarios.py 2010-02-01 04:49:05 +0000
44+++ lib/testscenarios/scenarios.py 2010-10-12 05:03:45 +0000
45@@ -2,6 +2,7 @@
46 # dependency injection ('scenarios') by tests.
47 #
48 # Copyright (c) 2009, Robert Collins <robertc@robertcollins.net>
49+# Copyright (c) 2010 Martin Pool <mbp@sourcefrog.net>
50 #
51 # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
52 # license at the users choice. A copy of both licenses are available in the
53@@ -18,8 +19,14 @@
54 'apply_scenario',
55 'apply_scenarios',
56 'generate_scenarios',
57+ 'load_tests_apply_scenarios',
58 ]
59
60+
61+from itertools import (
62+ chain,
63+ product,
64+ )
65 import unittest
66
67 from testtools.testcase import clone_test_with_new_id
68@@ -76,3 +83,49 @@
69 yield newtest
70 else:
71 yield test
72+
73+
74+def load_tests_apply_scenarios(*params):
75+ """Multiply out all tests in a module that have scenarios.
76+
77+ If this is referenced by the `load_tests` attribute of a module, then
78+ testloaders that implement this protocol will automatically arrange for
79+ the scenarios to be expanded. In this case it is not necessary (or
80+ desirable) to subclass TestWithScenarios.
81+
82+ Two different calling conventions for load_tests have been used, and this
83+ function should support both. Python 2.7 passes (loader, standard_tests,
84+ pattern), and bzr, nose and others have used (standard_tests,
85+ module, loader). See <http://pad.lv/607412>.
86+
87+ :param loader: A TestLoader.
88+ :param standard_test: The test objects found in this module before
89+ multiplication.
90+ """
91+ if getattr(params[0], 'suiteClass', None) is not None:
92+ loader, standard_tests, pattern = params
93+ else:
94+ standard_tests, module, loader = params
95+ result = loader.suiteClass()
96+ result.addTests(generate_scenarios(standard_tests))
97+ return result
98+
99+
100+def multiply_scenarios(*scenarios):
101+ """Multiply two or more iterables of scenarios.
102+
103+ It is safe to pass scenario generators or iterators.
104+
105+ :returns: A list of compound scenarios: the cross-product of all
106+ scenarios, with the names concatenated and the parameters
107+ merged together.
108+ """
109+ r = []
110+ scenario_lists = map(list, scenarios)
111+ products = product(*scenario_lists)
112+ for combo in products:
113+ names, params = zip(*combo)
114+ for p in params:
115+ d.update(p.iteritems())
116+ r.append((','.join(names), d))
117+ return r
118
119=== modified file 'lib/testscenarios/tests/test_scenarios.py'
120--- lib/testscenarios/tests/test_scenarios.py 2010-02-01 04:49:05 +0000
121+++ lib/testscenarios/tests/test_scenarios.py 2010-10-12 05:03:45 +0000
122@@ -2,6 +2,7 @@
123 # dependency injection ('scenarios') by tests.
124 #
125 # Copyright (c) 2009, Robert Collins <robertc@robertcollins.net>
126+# Copyright (c) 2010 Martin Pool <mbp@sourcefrog.net>
127 #
128 # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
129 # license at the users choice. A copy of both licenses are available in the
130@@ -21,6 +22,8 @@
131 apply_scenario,
132 apply_scenarios,
133 generate_scenarios,
134+ load_tests_apply_scenarios,
135+ multiply_scenarios,
136 )
137 import testtools
138 from testtools.tests.helpers import LoggingResult
139@@ -171,3 +174,70 @@
140 tests = list(apply_scenarios(ReferenceTest.scenarios, test))
141 self.assertEqual([('demo', {})], ReferenceTest.scenarios)
142 self.assertEqual(ReferenceTest.scenarios, tests[0].scenarios)
143+
144+
145+class TestLoadTests(testtools.TestCase):
146+
147+ class SampleTest(unittest.TestCase):
148+ def test_nothing(self):
149+ pass
150+ scenarios = [
151+ ('a', {}),
152+ ('b', {}),
153+ ]
154+
155+ def test_load_tests_apply_scenarios(self):
156+ suite = load_tests_apply_scenarios(
157+ unittest.TestLoader(),
158+ [self.SampleTest('test_nothing')],
159+ None)
160+ result_tests = list(testtools.iterate_tests(suite))
161+ self.assertEquals(
162+ 2,
163+ len(result_tests),
164+ result_tests)
165+
166+ def test_load_tests_apply_scenarios_old_style(self):
167+ """Call load_tests in the way used by pre-Python2.7 code.
168+
169+ See <https://bugs.launchpad.net/bzr/+bug/607412>
170+ """
171+ suite = load_tests_apply_scenarios(
172+ [self.SampleTest('test_nothing')],
173+ self.__class__.__module__,
174+ unittest.TestLoader(),
175+ )
176+ result_tests = list(testtools.iterate_tests(suite))
177+ self.assertEquals(
178+ 2,
179+ len(result_tests),
180+ result_tests)
181+
182+
183+class TestMultiplyScenarios(testtools.TestCase):
184+
185+ def test_multiply_scenarios(self):
186+ def s(name):
187+ for i in 'ab':
188+ yield i, {name: i}
189+ r = list(multiply_scenarios(s('p'), s('q')))
190+ self.assertEquals([
191+ ('a,a', dict(p='a', q='a')),
192+ ('a,b', dict(p='a', q='b')),
193+ ('b,a', dict(p='b', q='a')),
194+ ('b,b', dict(p='b', q='b')),
195+ ],
196+ r)
197+
198+ def test_multiply_many_scenarios(self):
199+ def s(name):
200+ for i in 'abc':
201+ yield i, {name: i}
202+ r = list(multiply_scenarios(s('p'), s('q'), s('r'), s('t')))
203+ self.assertEqual(
204+ 3**4,
205+ len(r),
206+ r)
207+ self.assertEqual(
208+ 'a,a,a,a',
209+ r[0][0])

Subscribers

People subscribed via source and target branches