source: trunk/server/lib/testtools/MANUAL@ 880

Last change on this file since 880 was 745, checked in by Silvan Scherrer, 13 years ago

Samba Server: updated trunk to 3.6.0

File size: 11.6 KB
Line 
1======
2Manual
3======
4
5Introduction
6------------
7
8This document provides overview of the features provided by testtools. Refer
9to the API docs (i.e. docstrings) for full details on a particular feature.
10
11Extensions to TestCase
12----------------------
13
14Custom exception handling
15~~~~~~~~~~~~~~~~~~~~~~~~~
16
17testtools provides a way to control how test exceptions are handled. To do
18this, add a new exception to self.exception_handlers on a TestCase. For
19example::
20
21 >>> self.exception_handlers.insert(-1, (ExceptionClass, handler)).
22
23Having done this, if any of setUp, tearDown, or the test method raise
24ExceptionClass, handler will be called with the test case, test result and the
25raised exception.
26
27Controlling test execution
28~~~~~~~~~~~~~~~~~~~~~~~~~~
29
30If you want to control more than just how exceptions are raised, you can
31provide a custom `RunTest` to a TestCase. The `RunTest` object can change
32everything about how the test executes.
33
34To work with `testtools.TestCase`, a `RunTest` must have a factory that takes
35a test and an optional list of exception handlers. Instances returned by the
36factory must have a `run()` method that takes an optional `TestResult` object.
37
38The default is `testtools.runtest.RunTest` and calls 'setUp', the test method
39and 'tearDown' in the normal, vanilla way that Python's standard unittest
40does.
41
42To specify a `RunTest` for all the tests in a `TestCase` class, do something
43like this::
44
45 class SomeTests(TestCase):
46 run_tests_with = CustomRunTestFactory
47
48To specify a `RunTest` for a specific test in a `TestCase` class, do::
49
50 class SomeTests(TestCase):
51 @run_test_with(CustomRunTestFactory, extra_arg=42, foo='whatever')
52 def test_something(self):
53 pass
54
55In addition, either of these can be overridden by passing a factory in to the
56`TestCase` constructor with the optional 'runTest' argument.
57
58TestCase.addCleanup
59~~~~~~~~~~~~~~~~~~~
60
61addCleanup is a robust way to arrange for a cleanup function to be called
62before tearDown. This is a powerful and simple alternative to putting cleanup
63logic in a try/finally block or tearDown method. e.g.::
64
65 def test_foo(self):
66 foo.lock()
67 self.addCleanup(foo.unlock)
68 ...
69
70Cleanups can also report multiple errors, if appropriate by wrapping them in
71a testtools.MultipleExceptions object::
72
73 raise MultipleExceptions(exc_info1, exc_info2)
74
75
76TestCase.addOnException
77~~~~~~~~~~~~~~~~~~~~~~~
78
79addOnException adds an exception handler that will be called from the test
80framework when it detects an exception from your test code. The handler is
81given the exc_info for the exception, and can use this opportunity to attach
82more data (via the addDetails API) and potentially other uses.
83
84
85TestCase.patch
86~~~~~~~~~~~~~~
87
88``patch`` is a convenient way to monkey-patch a Python object for the duration
89of your test. It's especially useful for testing legacy code. e.g.::
90
91 def test_foo(self):
92 my_stream = StringIO()
93 self.patch(sys, 'stderr', my_stream)
94 run_some_code_that_prints_to_stderr()
95 self.assertEqual('', my_stream.getvalue())
96
97The call to ``patch`` above masks sys.stderr with 'my_stream' so that anything
98printed to stderr will be captured in a StringIO variable that can be actually
99tested. Once the test is done, the real sys.stderr is restored to its rightful
100place.
101
102
103TestCase.skipTest
104~~~~~~~~~~~~~~~~~
105
106``skipTest`` is a simple way to have a test stop running and be reported as a
107skipped test, rather than a success/error/failure. This is an alternative to
108convoluted logic during test loading, permitting later and more localized
109decisions about the appropriateness of running a test. Many reasons exist to
110skip a test - for instance when a dependency is missing, or if the test is
111expensive and should not be run while on laptop battery power, or if the test
112is testing an incomplete feature (this is sometimes called a TODO). Using this
113feature when running your test suite with a TestResult object that is missing
114the ``addSkip`` method will result in the ``addError`` method being invoked
115instead. ``skipTest`` was previously known as ``skip`` but as Python 2.7 adds
116``skipTest`` support, the ``skip`` name is now deprecated (but no warning
117is emitted yet - some time in the future we may do so).
118
119TestCase.useFixture
120~~~~~~~~~~~~~~~~~~~
121
122``useFixture(fixture)`` calls setUp on the fixture, schedules a cleanup to
123clean it up, and schedules a cleanup to attach all details held by the
124fixture to the details dict of the test case. The fixture object should meet
125the ``fixtures.Fixture`` protocol (version 0.3.4 or newer). This is useful
126for moving code out of setUp and tearDown methods and into composable side
127classes.
128
129
130New assertion methods
131~~~~~~~~~~~~~~~~~~~~~
132
133testtools adds several assertion methods:
134
135 * assertIn
136 * assertNotIn
137 * assertIs
138 * assertIsNot
139 * assertIsInstance
140 * assertThat
141
142
143Improved assertRaises
144~~~~~~~~~~~~~~~~~~~~~
145
146TestCase.assertRaises returns the caught exception. This is useful for
147asserting more things about the exception than just the type::
148
149 error = self.assertRaises(UnauthorisedError, thing.frobnicate)
150 self.assertEqual('bob', error.username)
151 self.assertEqual('User bob cannot frobnicate', str(error))
152
153Note that this is incompatible with the assertRaises in unittest2/Python2.7.
154While we have no immediate plans to change to be compatible consider using the
155new assertThat facility instead::
156
157 self.assertThat(
158 lambda: thing.frobnicate('foo', 'bar'),
159 Raises(MatchesException(UnauthorisedError('bob')))
160
161There is also a convenience function to handle this common case::
162
163 self.assertThat(
164 lambda: thing.frobnicate('foo', 'bar'),
165 raises(UnauthorisedError('bob')))
166
167
168TestCase.assertThat
169~~~~~~~~~~~~~~~~~~~
170
171assertThat is a clean way to write complex assertions without tying them to
172the TestCase inheritance hierarchy (and thus making them easier to reuse).
173
174assertThat takes an object to be matched, and a matcher, and fails if the
175matcher does not match the matchee.
176
177See pydoc testtools.Matcher for the protocol that matchers need to implement.
178
179testtools includes some matchers in testtools.matchers.
180python -c 'import testtools.matchers; print testtools.matchers.__all__' will
181list those matchers.
182
183An example using the DocTestMatches matcher which uses doctests example
184matching logic::
185
186 def test_foo(self):
187 self.assertThat([1,2,3,4], DocTestMatches('[1, 2, 3, 4]'))
188
189
190Creation methods
191~~~~~~~~~~~~~~~~
192
193testtools.TestCase implements creation methods called ``getUniqueString`` and
194``getUniqueInteger``. See pages 419-423 of *xUnit Test Patterns* by Meszaros
195for a detailed discussion of creation methods.
196
197
198Test renaming
199~~~~~~~~~~~~~
200
201``testtools.clone_test_with_new_id`` is a function to copy a test case
202instance to one with a new name. This is helpful for implementing test
203parameterization.
204
205
206Extensions to TestResult
207------------------------
208
209TestResult.addSkip
210~~~~~~~~~~~~~~~~~~
211
212This method is called on result objects when a test skips. The
213``testtools.TestResult`` class records skips in its ``skip_reasons`` instance
214dict. The can be reported on in much the same way as succesful tests.
215
216
217TestResult.time
218~~~~~~~~~~~~~~~
219
220This method controls the time used by a TestResult, permitting accurate
221timing of test results gathered on different machines or in different threads.
222See pydoc testtools.TestResult.time for more details.
223
224
225ThreadsafeForwardingResult
226~~~~~~~~~~~~~~~~~~~~~~~~~~
227
228A TestResult which forwards activity to another test result, but synchronises
229on a semaphore to ensure that all the activity for a single test arrives in a
230batch. This allows simple TestResults which do not expect concurrent test
231reporting to be fed the activity from multiple test threads, or processes.
232
233Note that when you provide multiple errors for a single test, the target sees
234each error as a distinct complete test.
235
236
237TextTestResult
238~~~~~~~~~~~~~~
239
240A TestResult that provides a text UI very similar to the Python standard
241library UI. Key differences are that its supports the extended outcomes and
242details API, and is completely encapsulated into the result object, permitting
243it to be used without a 'TestRunner' object. Not all the Python 2.7 outcomes
244are displayed (yet). It is also a 'quiet' result with no dots or verbose mode.
245These limitations will be corrected soon.
246
247
248Test Doubles
249~~~~~~~~~~~~
250
251In testtools.testresult.doubles there are three test doubles that testtools
252uses for its own testing: Python26TestResult, Python27TestResult,
253ExtendedTestResult. These TestResult objects implement a single variation of
254the TestResult API each, and log activity to a list self._events. These are
255made available for the convenience of people writing their own extensions.
256
257
258startTestRun and stopTestRun
259~~~~~~~~~~~~~~~~~~~~~~~~~~~~
260
261Python 2.7 added hooks 'startTestRun' and 'stopTestRun' which are called
262before and after the entire test run. 'stopTestRun' is particularly useful for
263test results that wish to produce summary output.
264
265testtools.TestResult provides empty startTestRun and stopTestRun methods, and
266the default testtools runner will call these methods appropriately.
267
268
269Extensions to TestSuite
270-----------------------
271
272ConcurrentTestSuite
273~~~~~~~~~~~~~~~~~~~
274
275A TestSuite for parallel testing. This is used in conjuction with a helper that
276runs a single suite in some parallel fashion (for instance, forking, handing
277off to a subprocess, to a compute cloud, or simple threads).
278ConcurrentTestSuite uses the helper to get a number of separate runnable
279objects with a run(result), runs them all in threads using the
280ThreadsafeForwardingResult to coalesce their activity.
281
282
283Running tests
284-------------
285
286testtools provides a convenient way to run a test suite using the testtools
287result object: python -m testtools.run testspec [testspec...].
288
289To run tests with Python 2.4, you'll have to do something like:
290 python2.4 /path/to/testtools/run.py testspec [testspec ...].
291
292
293Test discovery
294--------------
295
296testtools includes a backported version of the Python 2.7 glue for using the
297discover test discovery module. If you either have Python 2.7/3.1 or newer, or
298install the 'discover' module, then you can invoke discovery::
299
300 python -m testtools.run discover [path]
301
302For more information see the Python 2.7 unittest documentation, or::
303
304 python -m testtools.run --help
305
306
307Twisted support
308---------------
309
310Support for running Twisted tests is very experimental right now. You
311shouldn't really do it. However, if you are going to, here are some tips for
312converting your Trial tests into testtools tests.
313
314 * Use the AsynchronousDeferredRunTest runner
315 * Make sure to upcall to setUp and tearDown
316 * Don't use setUpClass or tearDownClass
317 * Don't expect setting .todo, .timeout or .skip attributes to do anything
318 * flushLoggedErrors is not there for you. Sorry.
319 * assertFailure is not there for you. Even more sorry.
320
321
322General helpers
323---------------
324
325Lots of the time we would like to conditionally import modules. testtools
326needs to do this itself, and graciously extends the ability to its users.
327
328Instead of::
329
330 try:
331 from twisted.internet import defer
332 except ImportError:
333 defer = None
334
335You can do::
336
337 defer = try_import('twisted.internet.defer')
338
339
340Instead of::
341
342 try:
343 from StringIO import StringIO
344 except ImportError:
345 from io import StringIO
346
347You can do::
348
349 StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])
Note: See TracBrowser for help on using the repository browser.