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

Proposed by Martin Pool
Status: Merged
Merged at revision: not available
Proposed branch: lp:~mbp/testscenarios/mbp-doc
Merge into: lp:~testtools-committers/testscenarios/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~mbp/testscenarios/mbp-doc
Reviewer Review Type Date Requested Status
Robert Collins nice Pending
Review via email: mp+4300@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

Documentation is now more correct and tested

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2009-03-07 09:58:55 +0000
3+++ Makefile 2009-03-09 06:45:33 +0000
4@@ -1,9 +1,13 @@
5 PYTHONPATH:=$(shell pwd)/lib:${PYTHONPATH}
6+PYTHON ?= python
7
8 all:
9
10+# it would be nice to use doctest directly to run the README, but that's
11+# only supported from python2.6 onwards, so we need a script
12 check:
13- PYTHONPATH=$(PYTHONPATH) python ./test_all.py $(TESTRULE)
14+ PYTHONPATH=$(PYTHONPATH):.:./lib $(PYTHON) run_doctest.py README
15+ PYTHONPATH=$(PYTHONPATH) $(PYTHON) ./test_all.py $(TESTRULE)
16
17 clean:
18 find . -name '*.pyc' -print0 | xargs -0 rm -f
19
20=== modified file 'README'
21--- README 2009-03-08 04:37:33 +0000
22+++ README 2009-03-09 06:45:33 +0000
23@@ -1,5 +1,6 @@
24-testscenarios: extensions to python unittest to allow declarative
25-dependency injection ('scenarios') by tests.
26+*****************************************************************
27+testscenarios: extensions to python unittest to support scenarios
28+*****************************************************************
29
30 Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
31
32@@ -24,11 +25,13 @@
33 dependencies externally to the test code itself, allowing easy testing in
34 different situations).
35
36-Dependencies:
37-=============
38+Dependencies
39+============
40
41 * Python 2.4+
42-* testtools
43+* testtools <https://launchpad.net/testtools>
44+
45+ >>> import testtools
46
47
48 Why TestScenarios
49@@ -51,19 +54,33 @@
50 in multiple scenarios clear, easy to debug and work with even when the list
51 of scenarios is dynamically generated.
52
53-Getting Scenarios applied:
54-==========================
55+
56+Defining Scenarios
57+==================
58+
59+A **scenario** is a tuple of a string name for the scenario, and a dict of
60+parameters describing the scenario. The name is appended to the test name, and
61+the parameters are made available to the test instance when it's run.
62+
63+Scenarios are presented in **scenario lists** which are typically Python lists
64+but may be any iterable.
65+
66+
67+Getting Scenarios applied
68+=========================
69
70 At its heart the concept is simple. For a given test object with a list of
71 scenarios we prepare a new test object for each scenario. This involves:
72- * Clone the test to a new test with a new id uniquely distinguishing it.
73- * Apply the scenario to the test by setting each key, value in the scenario
74- as attributes on the test object.
75+
76+* Clone the test to a new test with a new id uniquely distinguishing it.
77+* Apply the scenario to the test by setting each key, value in the scenario
78+ as attributes on the test object.
79
80 There are some complicating factors around making this happen seamlessly. These
81 factors are in two areas:
82- * Choosing what scenarios to use. (See Setting Scenarios For A Test).
83- * Getting the multiplication to happen.
84+
85+* Choosing what scenarios to use. (See Setting Scenarios For A Test).
86+* Getting the multiplication to happen.
87
88 Subclasssing
89 ++++++++++++
90@@ -84,10 +101,21 @@
91 useful test base classes, or need to override run() or __call__ yourself) then
92 you can cause scenario application to happen later by calling
93 ``testscenarios.generate_scenarios()``. For instance::
94- >>> mytests = loader.loadTestsFromNames([...])
95- >>> test_suite = TestSuite()
96+
97+ >>> import unittest
98+ >>> from testscenarios.scenarios import generate_scenarios
99+
100+This can work with loaders and runners from the standard library, or possibly other
101+implementations::
102+
103+ >>> loader = unittest.TestLoader()
104+ >>> test_suite = unittest.TestSuite()
105+ >>> runner = unittest.TextTestRunner()
106+
107+ >>> mytests = loader.loadTestsFromNames(['example.test_sample'])
108 >>> test_suite.addTests(generate_scenarios(mytests))
109 >>> runner.run(test_suite)
110+ <unittest._TextTestResult run=1 errors=0 failures=0>
111
112 Testloaders
113 +++++++++++
114@@ -100,35 +128,37 @@
115 With ``load_tests``::
116
117 >>> def load_tests(standard_tests, module, loader):
118- >>> result = loader.suiteClass()
119- >>> result.addTests(generate_scenarios(standard_tests))
120- >>> return result
121+ ... result = loader.suiteClass()
122+ ... result.addTests(generate_scenarios(standard_tests))
123+ ... return result
124
125 With ``test_suite``::
126
127 >>> def test_suite():
128- >>> loader = TestLoader()
129- >>> tests = loader.loadTestsFromName(__name__)
130- >>> result = loader.suiteClass()
131- >>> result.addTests(generate_scenarios(tests))
132- >>> return result
133-
134-
135-Setting Scenarios for a test:
136-=============================
137+ ... loader = TestLoader()
138+ ... tests = loader.loadTestsFromName(__name__)
139+ ... result = loader.suiteClass()
140+ ... result.addTests(generate_scenarios(tests))
141+ ... return result
142+
143+
144+Setting Scenarios for a test
145+============================
146
147 A sample test using scenarios can be found in the doc/ folder.
148
149-See pydoc testscenarios for details.
150+See `pydoc testscenarios` for details.
151
152 On the TestCase
153 +++++++++++++++
154
155 You can set a scenarios attribute on the test case::
156
157- >>> class MyTest(TestCase):
158- >>>
159- >>> scenarios = [scenario1, scenario2, ...]
160+ >>> class MyTest(unittest.TestCase):
161+ ...
162+ ... scenarios = [
163+ ... ('scenario1', dict(param=1)),
164+ ... ('scenario2', dict(param=2)),]
165
166 This provides the main interface by which scenarios are found for a given test.
167 Subclasses will inherit the scenarios (unless they override the attribute).
168@@ -139,23 +169,41 @@
169 Test scenarios can also be generated arbitrarily later, as long as the test has
170 not yet run. Simply replace (or alter, but be aware that many tests may share a
171 single scenarios attribute) the scenarios attribute. For instance in this
172-example some third party tests are extended to run with a custom scenario.
173+example some third party tests are extended to run with a custom scenario. ::
174
175- >>> for test in iterate_tests(stock_library_tests):
176- >>> if isinstance(test, TestVFS):
177- >>> test.scenarios = test.scenarios + [my_vfs_scenario]
178- >>> ...
179+ >>> class TestTransport:
180+ ... """Hypothetical test case for bzrlib transport tests"""
181+ ... pass
182+ ...
183+ >>> stock_library_tests = unittest.TestLoader().loadTestsFromNames(
184+ ... ['example.test_sample'])
185+ ...
186+ >>> for test in testtools.iterate_tests(stock_library_tests):
187+ ... if isinstance(test, TestTransport):
188+ ... test.scenarios = test.scenarios + [my_vfs_scenario]
189+ ...
190+ >>> suite = unittest.TestSuite()
191 >>> suite.addTests(generate_scenarios(stock_library_tests))
192
193-Note that adding scenarios to a test that has already been parameterised via
194-generate_scenarios generates a cross product::
195- >>> class CrossProductDemo(TestCase):
196- >>> scenarios = [scenario_0_0, scenario_0_1]
197- >>> def test_foo(self):
198- >>> return
199+Generated tests don't have a ``scenarios`` list, because they don't normally
200+require any more expansion. However, you can add a ``scenarios`` list back on
201+to them, and then run them through ``generate_scenarios`` again to generate the
202+cross product of tests. ::
203+
204+ >>> class CrossProductDemo(unittest.TestCase):
205+ ... scenarios = [('scenario_0_0', {}),
206+ ... ('scenario_0_1', {})]
207+ ... def test_foo(self):
208+ ... return
209+ ...
210+ >>> suite = unittest.TestSuite()
211 >>> suite.addTests(generate_scenarios(CrossProductDemo("test_foo")))
212- >>> for test in iterate_tests(suite):
213- >>> test.scenarios = test.scenarios + [scenario_1_0, scenario_1_1]
214+ >>> for test in testtools.iterate_tests(suite):
215+ ... test.scenarios = [
216+ ... ('scenario_1_0', {}),
217+ ... ('scenario_1_1', {})]
218+ ...
219+ >>> suite2 = unittest.TestSuite()
220 >>> suite2.addTests(generate_scenarios(suite))
221 >>> print suite2.countTestCases()
222 4
223@@ -168,27 +216,28 @@
224 scenarios somewhere relevant to the tests that will use it, and then that can
225 be customised, or dynamically populate your scenarios from a registry etc.
226 For instance::
227+
228 >>> hash_scenarios = []
229 >>> try:
230- >>> import md5
231- >>> except ImportError:
232- >>> pass
233- >>> else:
234- >>> hash_scenarios.append(("md5", "hash": md5.new))
235+ ... from hashlib import md5
236+ ... except ImportError:
237+ ... pass
238+ ... else:
239+ ... hash_scenarios.append(("md5", dict(hash=md5)))
240 >>> try:
241- >>> import sha1
242- >>> except ImportError:
243- >>> pass
244- >>> else:
245- >>> hash_scenarios.append(("sha1", "hash": sha1.new))
246- >>>
247- >>> class TestHashContract(TestCase):
248- >>>
249- >>> scenarios = hash_scenarios
250- >>>
251- >>> class TestHashPerformance(TestCase):
252- >>>
253- >>> scenarios = hash_scenarios
254+ ... from hashlib import sha1
255+ ... except ImportError:
256+ ... pass
257+ ... else:
258+ ... hash_scenarios.append(("sha1", dict(hash=sha1)))
259+ ...
260+ >>> class TestHashContract(unittest.TestCase):
261+ ...
262+ ... scenarios = hash_scenarios
263+ ...
264+ >>> class TestHashPerformance(unittest.TestCase):
265+ ...
266+ ... scenarios = hash_scenarios
267
268
269 Forcing Scenarios
270@@ -201,3 +250,10 @@
271 ``apply_scenarios`` function does not reset the test scenarios attribute,
272 allowing it to be used to layer scenarios without affecting existing scenario
273 selection.
274+
275+
276+Advice on Writing Scenarios
277+===========================
278+
279+If a parameterised test is because of a bug run without being parameterized,
280+it should fail rather than running with defaults, because this can hide bugs.
281
282=== added directory 'example'
283=== added file 'example/__init__.py'
284--- example/__init__.py 1970-01-01 00:00:00 +0000
285+++ example/__init__.py 2009-03-09 06:45:33 +0000
286@@ -0,0 +1,1 @@
287+# contractual obligation
288
289=== added file 'example/test_sample.py'
290--- example/test_sample.py 1970-01-01 00:00:00 +0000
291+++ example/test_sample.py 2009-03-09 06:45:33 +0000
292@@ -0,0 +1,6 @@
293+import unittest
294+
295+class TestSample(unittest.TestCase):
296+
297+ def test_so_easy(self):
298+ pass
299
300=== added file 'run_doctest.py'
301--- run_doctest.py 1970-01-01 00:00:00 +0000
302+++ run_doctest.py 2009-03-09 06:45:33 +0000
303@@ -0,0 +1,5 @@
304+import doctest
305+import sys
306+
307+for n in sys.argv:
308+ print doctest.testfile(n, raise_on_error=False, )

Subscribers

People subscribed via source and target branches