2 min read

Doctest 101: Write Tests as You Write Code

When it comes to writing tests for your Python code, doctest offers an easy, readable, and Pythonic way to go about it. In this guide, we'll take a deep dive into how you can use doctest to write test cases right in your docstrings, making your code self-testing and more robust.
Doctest 101: Write Tests as You Write Code
Photo by JESHOOTS.COM / Unsplash

Introduction

When it comes to writing tests for your Python code, doctest offers an easy, readable, and Pythonic way to go about it. In this guide, we'll take a deep dive into how you can use doctest to write test cases right in your docstrings, making your code self-testing and more robust.

What is Doctest?

doctest is a Python standard library module that allows you to test your code by running examples embedded in your docstrings. This makes your documentation not just descriptive but also verifiable, ensuring that your code performs as expected.

Getting Started

The basic idea behind doctest is to write tests as part of your docstrings. Here's a simple example:

def add(a, b):
    """
    Adds two numbers together.
    
    >>> add(2, 3)
    5
    >>> add(-1, 1)
    0
    """
    return a + b

Running Doctests

Command-Line Interface

You can run doctest via the command line

python -m doctest your_module.py

Within Your Code

You can also run doctest programmatically within your code:

import doctest
doctest.testmod()

Writing Effective Tests

Handling Exceptions

You can test exception-raising functions like so:

def divide(a, b):
    """
    Divides a by b.
    
    >>> divide(4, 2)
    2.0
    >>> divide(1, 0)
    Traceback (most recent call last):
        ...
    ZeroDivisionError: division by zero
    """
    return a / b

Floating Point Numbers

Be careful with floating-point numbers due to rounding errors:

def square_root(x):
    """
    Returns the square root of x.
    
    >>> square_root(4)
    2.0
    >>> square_root(2)
    1.4142135623730951  # may vary
    """
    return x ** 0.5

Floating Point Numbers: Avoiding Pitfalls

Dealing with floating-point numbers can be a bit tricky because of rounding errors and imprecise representations. However, doctest provides ways to manage these issues effectively.

Using round()

One approach is to use Python's round() function to round the results to a specific decimal place:

def square_root(x):
    """
    Returns the square root of x, rounded to 2 decimal places.
    
    >>> round(square_root(2), 2)
    1.41
    """
    return x ** 0.5

String-based Comparison

If you want more control over the format, you can convert the numbers to strings and then compare:

def square_root(x):
    """
    Returns the square root of x as a string, rounded to 2 decimal places.
    
    >>> "{:.2f}".format(square_root_str(2))
    '1.41'
    """
    return x ** 0.5

Using math.isclose()

For more complex scenarios, you can use Python's math.isclose() method to check if two floating-point numbers are close enough to be considered equal:

import math

def test_square_root():
    """
    >>> math.isclose(square_root(2), 1.41421356237, rel_tol=1e-9)
    True
    """
    pass

This method allows you to specify a relative tolerance level, making it highly customizable.

By adopting these strategies, you can write more robust and accurate tests for functions that return floating-point numbers.

Advanced Features

Verbose Mode

You can run doctest in verbose mode to see a detailed log:

python -m doctest -v your_module.py

Skipping Tests

To skip a test, you can add a doctest: +SKIP directive:

>>> print("This will be skipped.")  # doctest: +SKIP

Conclusion

doctest provides a quick and easy way to write tests for your Python code, right within your docstrings. It encourages good documentation practices while ensuring that your code is robust and bug-free.

So go ahead, make your docstrings work for you by making them testable!