[Python-Dev] unittest2 plus class and module level fixtures in unittest
Michael Foord
fuzzyman at voidspace.org.uk
Mon Mar 8 01:51:02 CET 2010
Hello all,
unittest has seen quite a few new features since Python 2.6. For those
of you who might have missed the announcements in other places,
unittest2 is a backport of the new features in unittest to work with
Python 2.4-2.6. It is already being used for the development of distutils2:
http://pypi.python.org/pypi/unittest2/
<http://pypi.python.org/pypi/unittest2/>
In other news, class and module fixtures (setUpClass / tearDownClass /
setUpModule / tearDownModule) are now implemented in unittest (in trunk
- not yet merged to py3k). These features are tested but I'm sure there
are some lurking bugs or oddities, so please try them out. I have not
yet added documentation for them; I'll pull it out from this email as a
starting point.
I'd rather this thread didn't become *another* debate on the merit of
these features, but perhaps that is too much to hope for.
Below are some notes on how class and module fixtures work.
Class and module level fixtures are implemented in unittest.TestSuite.
When the test suite encounters a test from a new class then
tearDownClass from the previous class (if there is one) is called,
followed by setUpClass from the new class.
Similarly if a test is from a different module from the previous test
then tearDownModule from the previous module is run, followed by
setUpModule from the new module.
After all the tests have run the final tearDownClass and tearDownModule
are run.
Note that shared fixtures do not play well with [potential] features
like test parallelization and they break test isolation. They should be
used with care.
setUpClass and tearDownClass
--------------------------------------
These must be implemented as class methods.
import unittest
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls._connection = createExpensiveConnectionObject()
@classmethod
def tearDownClass(cls):
cls._connection.destroy()
If you want the setUpClass and tearDownClass on base classes called then
you must call up to them yourself. The implementations in
unittest2.TestCase are empty.
If an exception is raised during a setUpClass then the tests in the
class are not run and the tearDownClass is not run. Skipped classes will
not have setUpClass or tearDownClass run.
A setUpClass that raises a unittest2.SkipTest exception will currently
be reported as an error rather than a skip (although the effect is the
same). This will be fixed at some point in the future.
setUpModule and tearDownModule
--------------------------------------------
These should be implemented as functions.
def setUpModule():
createConnection()
def tearDownModule():
closeConnection()
If an exception is raised in a setUpModule then none of the tests in the
module will be run and the tearDownModule will not be run.
A setUpModule that raises a unittest.SkipTest exception will currently
be reported as an error rather than a skip (although the effect is the
same). This will be fixed at some point in the future.
The Gory Details
----------------------
The default ordering of tests created by the unittest test loaders is to
group all tests from the same modules and classes together. This will
lead to setUpClass / setUpModule (etc) being called exactly once per
class and module. If you randomize the order, so that tests from
different modules and classes are adjacent to each other, then these
shared fixture functions may be called multiple times in a single test run.
This particular issue was a point of some disussion. Shared fixtures are
not intended to work with suites with non-standard (incompatible)
ordering (a BaseTestSuite still exists for frameworks that don't want to
support shared fixtures), so this is already a pathological situation.
There are a couple of things unittest could do in this situation:
1) Work out the best order to run the setUp and tearDowns, based on
the position of the first and last test from each class / module in the
suite.
2) Run setUp and tearDowns appropriately if consecutive tests are
from different classes and modules.
If your setUps do unrelated things then strategy 1 will be the best one,
but (so long as your setups are repeatable) 2 will be fine just slower.
If your setups do related things, for example pushing large test
datasets into the same database, then having multiple setups active
before the corresponding teardowns have been run will actually do the
wrong thing and your tests will be broken. In addition strategy 1 relies
on unrolling the test suite and introspecting all tests prior to the
run, not all custom test suites implementations support this.
Given that strategy 2 was substantially easier to implement, and less
likely to cause broken test runs, that was what I went with. It is of
course possible to add alternative strategies, plus the metadata to
support them, if there proves to be a substantial need for this. One of
the major use cases is for test order randomization. It would be easy to
add a 'randomize' method to the suite that randomizes the order of test
modules in a suite, the order of test classes within modules and the
order of tests within a class. This would allow for tests to be run in a
random order whilst remaining compatible with shared fixtures.
If there are any exceptions raised during one of the shared fixture
functions the test is reported as an error. Because there is no
corresponding test instance an _ErrorHolder object (that has the same
interface as a TestCase) is created to represent the error. If you are
just using the standard unittest test runner then this detail doesn't
matter, but if you are a framework author it may be relevant.
I have a few minor features I'd like to add to unittest before 2.7 beta
- standard out capturing, fail fast and ctrl-c handling (first ctrl-c
will wait for current test to exit, second will force an exit - results
will still be reported). After that the *really* big problem for
unittest is the extensibility story.
I'll be working on extensibility points for unittest that allow people
to implement extensions without having to override functionality. This
will take a fair bit of discussion and experimentation, so I will
probably trial this in unittest2 first. It would be nice to have
something in place for the release of 3.2, but that may be ambitious.
All the best,
Michael Foord
--
http://www.ironpythoninaction.com/
http://www.voidspace.org.uk/blog
READ CAREFULLY. By accepting and reading this email you agree, on behalf of your employer, to release me from all obligations and waivers arising from any and all NON-NEGOTIATED agreements, licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap, confidentiality, non-disclosure, non-compete and acceptable use policies ("BOGUS AGREEMENTS") that I have entered into with your employer, its partners, licensors, agents and assigns, in perpetuity, without prejudice to my ongoing rights and privileges. You further represent that you have the authority to release me from any BOGUS AGREEMENTS on behalf of your employer.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20100308/0e816607/attachment.html>
More information about the Python-Dev
mailing list