DEV Community

chaitdwivedi
chaitdwivedi

Posted on • Edited on

Documenting your way to better tests in Python

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

Top comments (0)