[Zope-Coders] Help with Unit Testing: safe imports
Tres Seaver
tseaver@zope.com
19 Aug 2002 13:49:17 -0400
On Mon, 2002-08-19 at 11:58, Jeremy Hylton wrote:
> >>>>> "TS" == Tres Seaver <tseaver@zope.com> writes:
>
> TS> On Mon, 2002-08-19 at 10:01, Guido van Rossum wrote:
> >> > Python's own 'unittest.py' fails silently if the test module
> >> > bails with import errors.
> >> >
> >> > Successful import of the module-under-test should *not* be
> >> > assumed by the test module..
> >>
> >> I don't understand this. Can you describe a scenario in more
> >> detail?
>
> TS> Constructing a suite of tests for an entire package is tricky:
> TS> what is the framework supposed to do if it encounters a module
> TS> which looks like a test (has the correct name, imports unittest,
> TS> etc.) but which raises an ImportError at module scope?
>
> Then the tool running the tests should report this as a failure. I
> think the the problem, dear Tres, lies not in our tests, but in our
> tools. <wink>.
>
> TS> The tests which *should* have been run are unavailable, due to
> TS> the partial import, so the reporting machinery can't log them as
> TS> failed tests. The execution environment *may* try to report the
> TS> problem, but the "standard" ones do not do so. For instance,
> TS> with module-scope imports, the stock unittestgui.py will show a
> TS> "green bar", with a reduced number of passing tests.
>
> I'd report this as a bug on unittestgui.py.
>
> TS> The idiom I propose (import the module-under-test only within
> TS> the context of 'testFoo' method) has netiher disadvantage:
> TS> import errors are caught and reported as test failures, without
> TS> any need for additional reporting machinery.
>
> But it makes writing the tests more tedious because they have to
> include lots of repetitive boilerplate. If the boilerplate were truly
> necessary that would be okay. I live with Py_INCREF() and Py_DECREF()
> after all. But we can just fix the tools.
Note that the stock 'unittest.py' swallows ImportErrors:
def loadTestsFromName(self, name, module=None):
"""Return a suite of all tests cases given a string specifier.
The name may resolve either to a module, a test case class, a
test method within a test case class, or a callable object which
returns a TestCase or TestSuite instance.
The method optionally resolves the names relative to a given
module.
"""
parts = string.split(name, '.')
if module is None:
if not parts:
raise ValueError, "incomplete test name: %s" % name
else:
parts_copy = parts[:]
while parts_copy:
try:
module = __import__(string.join(parts_copy,'.'))
break
except ImportError:
del parts_copy[-1]
if not parts_copy: raise
parts = parts[1:]
obj = module
for part in parts:
obj = getattr(obj, part)
Note that this does *not* yield appropriate results:
----------------- begin sample ---------------
$ ll
total 16
drwxr-xr-x 3 tseaver tseaver 4096 Aug 19 13:44 ./
drwxrwxrwt 15 root root 8192 Aug 19 13:44 ../
drwxr-xr-x 3 tseaver tseaver 4096 Aug 19 13:36 foo/
lrwxrwxrwx 1 tseaver tseaver 30 Aug 19 13:39 unittest.py ->
/usr/lib/python2.1/unittest.py
$ find .
.
./foo
./foo/bar
./foo/bar/baz
./foo/bar/baz/__init__.py
./foo/bar/baz/tests
./foo/bar/baz/tests/__init__.py
./foo/bar/baz/tests/testBaz.py
./foo/bar/baz/Baz.py
./foo/bar/__init__.py
./foo/__init__.py
./unittest.py
$ cat foo/bar/baz/Baz.py
import Qux # Note: this won't import!
class Baz:
pass
$ cat foo/bar/baz/tests/testBaz.py
import unittest
from foo.bar.baz.Baz import Baz
class BazTests( unittest.TestCase ):
def test_something( self ):
pass
$ PYTHONPATH=`pwd` python2.1 unittest.py foo.bar.baz.tests.testBaz
Traceback (most recent call last):
File "unittest.py", line 712, in ?
main(module=None)
File "unittest.py", line 663, in __init__
self.parseArgs(argv)
File "unittest.py", line 690, in parseArgs
self.createTests()
File "unittest.py", line 696, in createTests
self.module)
File "unittest.py", line 458, in loadTestsFromNames
suites.append(self.loadTestsFromName(name, module))
File "unittest.py", line 433, in loadTestsFromName
obj = getattr(obj, part)
AttributeError: 'foo.bar.baz.tests' module has no attribute 'testBaz'
----------------- end sample -----------------
The ImportError (in Baz.py) is swallowed, leaving testBaz.py in
a "partially imported" mess.
Tres.
--
===============================================================
Tres Seaver tseaver@zope.com
Zope Corporation "Zope Dealers" http://www.zope.com