DEV Community

loading...

Documenting your way to better tests in Python

chaitdwivedi
Writing Software for Hardware Engineers!
・3 min read

Writing and Reading Code

Writing code is hard, reading code is harder

I participate in a lot of code reviews and one thing I've realized is - developers spend most of their time coding a solution, not enough time explaining it.

There are two ways you could explain how your code works:

  1. Write simple code that is self-explanatory
  2. Write "good" documentation

Value of Good Documentation

In this post, I am going to make a case for how writing good documentation can improve the quality of your code and should be done along with writing self-explanatory code.

Let me illustrate with an example.

I've defined my_function below, that computes some "arbitrary" logic:

def my_function(input_string, input_list=None):
    head = "Start:"
    tail = "" if not input_list else " ".join(input_list)
    output = f"{input_string} {tail}"
    if not input_string.startswith(head):
        output = f"{head} {input_string} {tail}"

    return output.strip()
Enter fullscreen mode Exit fullscreen mode

In a real-world problem, this piece of code might be much more complex.

As a reader, you could read this code and understand what it is doing. However, I can make the reader's job easier by using docstring.

Docstrings: Make code easy to read

Docstring holds immense value when developing code in a large organization and/or in a collaborative community.

It lets any developer understand what's happening in the function/module without them having to read through the entire codebase.

It can also be used with tools like sphinxdoc to generate beautiful documentation for your project.

def my_function(input_string, input_list=None):
    """Sanitize input string and append extra text if required

    The function checks if input_string starts with 'Start:' 
    if not, it will add the string to the input_string 

    It also converts input_list to a string using join 
    and appends to the input_string
    """
    head = "Start:"
    tail = "" if not input_list else " ".join(input_list)
    output = f"{input_string} {tail}"
    if not input_string.startswith(head):
        output = f"{head} {input_string} {tail}"

    return output.strip()
Enter fullscreen mode Exit fullscreen mode

Docstring in the above code explains what the code is doing, but it still feels like a repetition of what is written in the code block.

How do we improve this?
Using doctest!

Doctest: Read, Test and Document better

doctest allows you to not only test interactive Python examples but also makes sure your documentation is up to date.

Let us take a look at improved docstring for the same function using doctest

def my_function(input_string, input_list=None):
    """Sanitize input string and append extra text if required

    >>> my_function('hi')
    'Start: hi'

    >>> my_function('Start: some string')
    'Start: some string'

    >>> my_function('hi', ['other', 'item'])
    'Start: hi other item'

    :param input_string: string to sanitize
    :type input_string: str
    :param input_list: extra items to append, defaults to None
    :type input_list: list, optional
    :return: sanitized string
    :rtype: str
    """
    head = "Start:"
    tail = "" if not input_list else " ".join(input_list)
    output = f"{input_string} {tail}"
    if not input_string.startswith(head):
        output = f"{head} {input_string} {tail}"

    return output.strip()
Enter fullscreen mode Exit fullscreen mode

Here, I have defined some input-output examples for the given function which illustrate what the function is doing. For instance:

my_function('hi', ['other', 'item'])
Enter fullscreen mode Exit fullscreen mode

should return:

'Start: hi other item'
Enter fullscreen mode Exit fullscreen mode

The above documentation tells developers/readers the following:

  • What the function does
  • Parameters and their types
  • Return value and its type
  • Expected behavior - describes input/output examples

Conclusion

Generating documentation

I used sphinxdoc to generate documentation for the above function:

image

Practicing TDD

Writing documentation in Python also allows me to follow Test Driven Development, where I first define the behavior in docstring then write the code.

doctest can be run by any testing framework like unittest or pytest

$ pytest --doctest-modules -vv top.py
=================== test session starts ======================
platform darwin -- Python 3.8.2, pytest-6.2.4 
-- /projects/virtualenvs/dev-to/bin/python3
cachedir: .pytest_cache
rootdir: /Users/chaitanyadwivedi/projects/dev-to
collected 1 item                                                                                                                                           

top.py::top.my_function PASSED                          [100%]

==================== 1 passed in 0.05s =======================
Enter fullscreen mode Exit fullscreen mode

Discussion (0)

Forem Open with the Forem app