Recipe 8.10. Using doctest with unittest in Python 2.4
Credit: John Nielsen
Problem
You want to write some
unit tests for your code using
doctest's easy and intuitive
approach. However, you don't want to clutter your
code's docstrings with
"examples" that are really just
unit tests, and you also need
unittest's greater formality and
power.
Solution
Say you have a typical use of doctest such as the
following toy example module toy.py:
def add(a, b):Having a few example uses in your functions'
"" Add two arbitrary objects and return their sum.
>>> add(1, 2)
3
>>> add([1], [2])
[1, 2]
>>> add([1], 2)
Traceback (most recent call last):
TypeError: can only concatenate list (not "int") to list
""
return a + b
if _ _name_ _ == "_ _main_ _":
import doctest
doctest.testmod( )
docstrings, with doctest to check their accuracy,
is great. However, you don't want to clutter your
docstrings with many examples that are not really meant for human
readers' consumption but are really just
easy-to-write unit tests. With Python 2.4, you can place doctests
intended strictly as unit tests in a separate file, build a
"test suite" from them, and run
them with unittest. For example, place in file
test_toy.txt the following lines (no quoting
needed):
>>> import toyand add at the end of toy.py a few more lines:
>>> toy.add('a', 'b')
'ab'
>>> toy.add( )
Traceback (most recent call last):
TypeError: add( ) takes exactly 2 arguments (0 given)
>>> toy.add(1, 2, 3)
Traceback (most recent call last):
TypeError: add( ) takes exactly 2 arguments (3 given)
import unittestNow, running python toy.py at a shell command
suite = doctest.DocFileSuite('test_toy.txt')
unittest.TextTestRunner( ).run(suite)
prompt produces the following output:
.
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
Discussion
The doctest module of the Python Standard Library
is a simple, highly productive way to produce a plain but useful
bunch of unit tests for your code. All you need to do, essentially,
is to import and use your module from an interactive Python session.
Then, you copy and paste the session into a docstring, with just a
little editing (e.g. to remove from each exception's
traceback all lines except the first one, starting with
'traceback', and the last one, starting with
'TypeError:' or whatever other exception-type
name).
DocstringsDocumentation strings (docstrings) are an important feature that Python offers to help you document your code. Any module, class, function or method can have a string literal as its very first "statement". If so, then Python considers that string to be the docstring for the module, class, function, or method in question and saves it as the _ _doc_ _ attribute of the respective object. Modules, classes, functions, and methods that lack docstrings have None as the value of their _ _doc_ _ attribute.In Python's interactive interpreter, you can examine the "docstring" of an object, as well as other helpful information about the object, with the command help(theobject). Module pydoc, in the Python Standard Library, uses docstrings, as well as introspection, to generate and optionally serve web pages of information about modules, classes, functions, and methods. (See http://pydoc.org/ for a web site containing pydoc-generated documentation about the Python Standard Library as well as the standard Python online documentation.) |
is quite a bit more powerful, so you can produce more advanced sets
of unit tests and run them in more sophisticated ways. Writing the
unit tests is not quite as simple and fast as with
doctest.Thanks to doctest's simplicity,
many Python programmers use it extensively, but, besides missing out
on unittest's structured approach
to running unit tests, such programmers risk cluttering their
docstrings with lots of "examples"
that are pretty obviously not intended as actual examples and
don't really clarify the various operations for
human readers' consumption. Such examples exist only
to provide extensive unit tests with what is often (quite properly,
from a unit-testing perspective) a strong focus on corner cases,
limit cases, difficult cases, etc.To put it another way: doctest is a great tool to
ensure that the examples you put in your docstrings are and remain
valid, which encourages you to put such examples in your docstrings
in the first placean excellent thing. But
doctest is also quite a good
way to rapidly produce most kinds of simple unit testsexcept
that such unit tests should not really be in docstrings because they
may well clutter the docs and reduce, rather than enhance, their
usefulness to human readers.Python 2.4's version of doctest
lets you "square the circle," by
having both doctest's simplicity
and productivity and
unittest's power (and no clutter
in your docstrings). Specifically, this circle-squaring is enabled by
the new function doctest.DocFileSuite. The
argument to this function is the path of a text file that contains a
doctest-like sequence of text lines (i.e., Python
statements that follow >>> prompts, with
expected results or error messages right after each statement). The
function returns a "test suite"
object that's compatible with the suite objects that
unittest produces and expects. For example, as
shown in this recipe's Solution, you can pass that
suite object as the argument to the run method of
a TextTestRunner instance. Note that the text file
you pass to doctest.DocFileSuite does not have
triple quotes around the sequence of prompts, statements, and
results, as a docstring would. Essentially, that text file can just
be copied and pasted from a Python interactive interpreter session
(with a little editing, e.g., of exceptions'
tracebacks, as previously mentioned).
See Also
Documentation for standard library modules
unittest and doctest in the
Language Reference and Python in a
Nutshell.