Using doctest instead of unittest in Python

One of the points in The Zen of Python is there should ideally be one way to do things:

$ python -m this|grep  'only one'
There should be one-- and preferably only one --obvious way to do it.

Despite this, there is actually another module for writing automated tests in the standard library. Following on from last week's post, this post is going to look at using doctest to replace unittest.

An quick intro to doctest

The example from last week's post created two functions:

def add(*args):
    return sum(args)

def multiply(*args):
    return reduce((lambda x, y: x * y), args

As the name suggests, tests are added directly to the doc string of the function being tested. For example:

def add(*args):
    """Return the sum of all arguments

    Integers can be added:
    >>> add(2, 2)
    4
    """
    return sum(args)

The doctest.testmod function can then be used to run the example, and compare the expected output by adding code similar to the following:

if __name__ == "__main__":
    import doctest
    doctest.testmod()

And then running the Python script:

$ python example.py  -v
Trying:
    add(2, 2)
Expecting:
    4
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.add
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

Note: the -v options is required to show verbose output for passing tests. By default if a test passes no output is shown.

Adding additional tests is just a case of adding more examples to the doc string. For reference feel free to compare the example from last week's post with a modified version which uses doctest.

Return codes

By default running a script which calls doctes.testmod() will always return a zero exit code, even if tests fail:

$ cat example.py
def example():
    """This test will fail:
    >>> example()
    False
    """
    return True

if __name__ == "__main__":
    import doctest
    doctest.testmod()

$ python example.py
**********************************************************************
File "example.py", line 3, in __main__.example
Failed example:
    example()
Expected:
    False
Got:
    True
**********************************************************************
1 items had failures:
   1 of   1 in __main__.example
***Test Failed*** 1 failures.

$ echo $?
0

To get around this exit can be called explicitly if any tests fail:

if __name__ == "__main__":
    import doctest
    if doctest.testmod().failed > 0:
        exit(1)

Further reading

If you're interested in learning more about doctest I would recommend reading the Official doctest docs and having a look at Testing Through Documentation by Doug Hellmann.