[TIP] Fwd: Nose and doctests in extension modules...

Fernando Perez fperez.net at gmail.com
Fri Jun 20 19:53:15 PDT 2008


On Fri, Jun 20, 2008 at 10:31 AM, Fernando Perez <fperez.net at gmail.com> wrote:

> For ipython I already have fairly complex doctest-finding needs, so I
> may end up fixing this in the process.  I'll keep you posted, and if I
> get something working I'll get in touch with Titus.

This was getting in my way, so here's a quick and dirty solution.  I'm
attaching a tarball that contains an example, just run

make extest

to see the effect, AFTER you have Cython installed (it needs it to
build the extension module).  For reference, here's the output:

./extest.py
*************** Before monkeypatching doctest *****************
1 items had no tests:
    primes
0 tests in 1 items.
0 passed and 0 failed.
Test passed.
*************** After monkeypatching doctest *****************
Trying:
    primes(1)
Expecting:
    [2]
ok
Trying:
    primes(10)
Expecting:
    [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
ok
1 items had no tests:
    primes
1 items passed all tests:
   2 tests in primes.primes
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

###

As you can see, after applying my monkeypatch, doctests in the
extension module are correctly found, whereas early none were seen.

The fix is fairly trivial, if unpleasant (this is in extest.py):

# This class is used to monkeypatch doctest, which fails to recognize doctests
# in extension modules.  The single change is to add an isbuiltin() call in the
# recursion loop, since functions defined in extension modules are seen by
# inspect as 'builtins' (though they are not).

# Hold a reference to the original, which we'll need since we'll be
# monkeypatching doctest and would otherwise get an infinite recursion.
DocTestFinderOri = doctest.DocTestFinder

class DocTestFinder(doctest.DocTestFinder):

   def _find(self, tests, obj, name, module, source_lines, globs, seen):
        """
        Find tests for the given object and any contained objects, and
        add them to `tests`.
        """

        DocTestFinderOri._find(self,tests, obj, name, module,
        source_lines, globs, seen)

        # Look for tests in a module's contained objects.
        if inspect.ismodule(obj) and self._recurse:
            for valname, val in obj.__dict__.items():
                valname = '%s.%s' % (name, valname)
                # Recurse to functions & classes.
                if ((inspect.isfunction(val) or inspect.isclass(val) or
                     inspect.isbuiltin(val)) and
                    self._from_module(module, val)):
                    self._find(tests, val, valname, module, source_lines,
                               globs, seen)

###

Because doctest is a rigid and entangled piece of code, the only way
to fix this is to subclass and monkeypatch: testmod() doesn't take
callable entities for finder/runner/anything), and the finding method
also doesn't allow for any kind of flexible extension.

Feel free to apply this monkeypatch to doctest inside nose, it may not
be perfect but it seems to work already in simple cases.  I filed a
bug report against doctest as well:

http://bugs.python.org/issue3158

Cheers,

f
-------------- next part --------------
A non-text attachment was scrubbed...
Name: primes.tgz
Type: application/x-gzip
Size: 25533 bytes
Desc: not available
Url : http://lists.idyll.org/pipermail/testing-in-python/attachments/20080620/4708b152/attachment-0001.bin 


More information about the testing-in-python mailing list