Python Tip of the Day: Easy Doctest-ing
October 19, 2005
In Python you can add a description to any class or function. This description is called a docstring in the Python community.
def addOneToNumber(num): """this function adds one to the number parameter passed in. If not a number will throw InputError"""
`if isinstance(num, int) == True: return num + 1 else: raise InputError, “Must be a number”
`
Docstrings document what the function does, just as this docustring does here. Often with a description you want to include an example for future programmers, showing them how to call the function, or what they might expect what happens.
It is a great idea to put examples in your docstrings, but how can you be certain that the examples are up to date? Programmers really hate reading outdated sample code, is there a way to prevent this?
Python provides the doctest module which presents a solution to this problem: docstring will run statements in the docstring, and error if the output is different from what it expected. These docstrings are created by importing your script into a running Python interpreter, running your function, and pasting the command and the results into your docstring.
There’s an easy way to doctest your routines, read on to discover how…
You have a function you want to doctest. The annoying thing is all the setup work. Watch:
$ python Python 2.3.5 (#1, Mar 20 2005, 20:38:20) [GCC 3.3 20030304 (Apple Computer, Inc. build 1809)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.path.append("/Path/To/Wherever/This/File/Is/") >>> from myfile import *
(Press Control-D to exit the Python interpreter.)
sys.path
is where Python will go looking for modules to import. We add the path to the directory that stores the file we are interested in testing. Then we import the file, pulling all of the items from that module into the global namespace.
Now we are ready to test. Finally, after many steps we are ready to go. You know, all of these steps are pretty annoying, especially if we’re doing a lot of doctesting. There is indeed a better way. Watch:
$ python -i -c "import sys; sys.path.append('/Path/To/Wherever/This/File/Is/'); from myfile import *"
We call Python and have it open the interpreter (-i) after executing the code (-c) to add our module to the sys.path and import it. Note that the order of these parameters are important: it will not work the other way around
Calling Python with this command is so much easier than typing in three lines of code every time you want to add a new command to your module. You can even use your shell’s history feature and go back to the last command (usually this is a one keystroke).
Now we can add some doc tests to our script. In our Python interpreter, call addOneToNumber()
>>> addOneToNumber(45) 46
Copy these two line into your docstring. You must make sure that the indentation level of the doctest matches the rest of the docstring, so indent or outdent so they match.
Now go back to the Python interpreter:
>>> addOneToNumber("43") Traceback (most recent call last): File "", line 1, in ? File "/Users/aias/Desktop/addOneToNumber.py", line 10, in addOneToNumber raise TypeError, "Must be a number" TypeError: Must be a number
It is common courtesy to remove the tracebacks from docstrings. Copy the first line (the function call line), the second line (“Traceback…”) and the last line (“TypeError…”) and paste them into your docstring.
From here your docstring should look like:
def addOneToNumber(num): """this function adds one to the number parameter passed in. If not a number will throw InputError >>> addOneToNumber(45) 46
`>>> addOneToNumber(“43”) Traceback (most recent call last): TypeError: Must be a number
"""`
To run the doctests add the following lines to your python script (preferably somewhere where it won’t be triggered when your import the module. A good place is in a if __name__ == "__main__"
check.)
import doctest, sys doctest.testmod(sys.modules[__name__])
doctest will output nothing if all of your tests pass, and will complain (with great detail) when a test’s output does not match what it expected.
Unit testing is good, and doctest can be seem as unit-testing lite. You can quickly test one or two aspects of your function, while providing examples on how to call your function (or what to expect) for future maintainers. However, if your function has more than 3 or 4 doctests inside the docstring, consider moving your tests over to a unittest based test case. unittests are meant to be in files separate from the code they are testing, and this means they don’t clutter up your code testing 20 variations of some function. You can even call doctests from unittest, giving you the best of both worlds.
So, enjoy doctesting, and enjoy the trick of passing -i -c ”…” to the Python interpreter to have it “automatically” import your test module for you!