Docstrings and Doctests in Python: A Comprehensive Guide
1. Docstrings in Python
Python is a popular high-level programming language known for its readability and simplicity. Its features make it suitable for both beginners and advanced programmers. One of the features that make Python so easy to use is its documentation tools. Python documentation tools provide easy-to-use mechanisms for documenting code, including docstrings and doctests.
When you write Python code, you often write functions that perform specific tasks. As others begin to use your code, it’s essential to have a way to describe the intended behavior of your functions. That’s where docstrings come in.
1.1 Definition and Purpose of Docstrings
A docstring is a type of string that provides a description of the function’s purpose, behavior, arguments, and returns. The beauty of using docstrings is that they can be accessed programmatically, making it possible to generate documentation automatically.
The primary purpose of docstrings is to help developers understand how to use functions by reading their descriptions.
1.2 Example of a Function with Docstring
Let’s look at an example of a function with a docstring. Suppose we want to write a function that finds the factorial of an integer using a recursive method.
def factorial(n):
''' returns the factorial of a non-negative integer n
Args:
n: positive integer
Returns:
the factorial of n
'''
if n==0:
return 1
else:
return n*factorial(n-1)
In this example, the docstring is in triple quotes and is immediately below the function definition. The docstring contains information about the function’s purpose, the arguments, and the return value.
2. Doctests in Python
Doctests are another form of documentation in Python. However, unlike docstrings, they contain executable code.
The primary purpose of doctests is to provide a way to test functions while documenting their behavior.
2.1 Definition and Purpose of Doctests
A doctest is a test case expressed in a docstring. The idea is that you provide a short example of how the function is used and what the expected output is.
The Python interpreter can use the docstring to verify that the function produces the expected output. The primary purpose of doctests is to provide examples of how to use a function while verifying its output.
2.2 Example of Using Doctests in a Function
Let’s look at an example of how to use doctests. We’ll use the same factorial function that we used earlier, but this time we’ll add some doctests.
def factorial(n):
''' returns the factorial of a non-negative integer n
Args:
n: positive integer
Returns:
the factorial of n
'''
if n==0:
return 1
else:
return n*factorial(n-1)
>>> factorial(5)
120
>>> factorial(4)
24
In this example, we’ve added two examples of how to use the function with the expected output. The “>>>” symbol indicates that the line is a doctest.
To run the tests, we can use the doctest module:
if __name__ == "__main__":
import doctest
doctest.testmod()
This will run all the doctests in the file and output the results.
3. Successful Doctests in Python
Doctests are a way to document Python code while also testing it.
In this section, we’ll cover how to use the doctest module to run tests and the output of successful doctests in Python.
3.1 Using doctest module to run tests
Python comes with a built-in doctest module that you can use to run doctests. The module provides a testmod() function that you can use to run all doctests in a file.
Here’s how to use the doctest module to run tests in Python:
import doctest
def factorial(n):
'''
>>> factorial(5)
120
>>> factorial(4)
24
'''
if n == 1:
return 1
else:
return n * factorial(n-1)
if __name__ == '__main__':
doctest.testmod()
When you run this script, it will output the results of the doctests. Here’s what the output looks like when all tests pass:
**********************************************************************
File ".../doctest_example.py", line 4, in __main__.factorial
Failed example:
factorial(5)
Expected:
120
Got:
120
**********************************************************************
File ".../doctest_example.py", line 6, in __main__.factorial
Failed example:
factorial(4)
Expected:
24
Got:
24
**********************************************************************
1 items had failures:
2 of 2 in __main__.factorial
***Test Failed*** 2 failures.
Here, you can see that the output shows that all tests passed.
It includes information about each test, including the expected output and the actual output.
3.2 Output of Successful Doctests in Python
When all tests have passed, the output gives information on the number of tests run and the number of tests that have passed. It also includes a message which reads “Test passed”.
Here’s an example of a successful doctest output:
Trying:
factorial(3)
Expecting:
6
ok
Trying:
factorial(4)
Expecting:
24
ok
Trying:
factorial(5)
Expecting:
120
ok
Trying:
factorial(0)
Expecting:
1
ok
1 items had no tests:
__main__
1 items passed all tests:
4 tests in __main__.factorial
4 tests in 2 items. 4 passed and 0 failed.
Test passed.
In this example, all tests have passed, and the doctest module has successfully verified that the function behaves as expected.
4. Failed Doctests in Python
In some cases, doctests may fail.
This might happen if the function is not working as expected, or if the doctest is incorrect. In this section, we’ll cover an example of a failed doctest and the output you can expect in Python.
4.1 Example of Failed Doctests in Python
Suppose we have a function that takes a list as an argument and returns the sum of its elements. Here’s the function definition, along with some doctests:
def sum_list(lst):
"""
Returns the sum of elements in a list.
>>> sum_list([1, 2, 3])
6
>>> sum_list([0, -1, 1])
0
>>> sum_list(['a', 'b', 'c'])
TypeError
"""
try:
return sum(lst)
except TypeError:
return TypeError
In this example, the first two tests pass, but the third test fails, as it raises a TypeError. This indicates that the function is not working correctly for this input.
4.2 Output of Failed Doctests in Python
When a test fails, the doctest module outputs a message indicating the failure and the expected and actual output. It also gives information about the number of tests passed and the number of tests that have failed.
Here’s what the output looks like when the doctest fails:
**********************************************************************
File ".../doctest_example.py", line 10, in __main__.sum_list
Failed example:
sum_list(['a', 'b', 'c'])
Expected:
TypeError
Got:
**********************************************************************
1 items had failures:
1 of 3 in __main__.sum_list
***Test Failed*** 1 failures.
In this example, the doctest module outputs a message stating that there is one failed test.
The message includes information about where the failure occurred, the expected output, and the actual output.
Overall, doctests are a highly useful method of documenting and testing Python code.
By incorporating doctest into your development process, you can save time on manual testing and ensure that your code functions correctly. However, it’s important to remember that like any testing technique, doctests can produce incorrect or unexpected results and should be used in conjunction with other forms of testing to ensure code reliability.
5. Use of Doctests in Python
In this section, we’ll discuss the advantages of using doctests in Python and provide tips for writing effective and useful doctests.
5.1 Advantages of Using Doctests
The primary purpose of doctests is to provide a way to document code while also testing it. There are numerous advantages to using doctests in Python, including:
- Easy to write and maintain: Doctests are easy to write and can be incorporated into the code. They become part of the code documentation, and as such, they maintain with the code.
- Ensures code reliability: Doctests provide quick feedback about code behavior, which helps ensure code reliability.
- Increases code readability: By including doctests in the code, you make it easier for other developers to understand how your code works and can reduce errors or confusion down the line.
- Provides testing for edge cases: Doctests can be used to test code for edge cases and input outliers, ensuring that the code functions as intended, even for non-standard inputs.
5.2 Tips for Writing Doctests
While doctests can be very useful, they need to be written appropriately to be effective. Here are some tips to help you write useful and successful doctests in Python.
- Test expected output: Make sure your doctests test the expected outputs and behaviors of your code, particularly for edge cases or input outliers.
- Use descriptive docstrings: Write descriptive docstrings that explain the purpose of the code and the function’s inputs and outputs.
- Test iteratively: Test your code iteratively as you write new functions or make changes to existing ones. By building up test cases over time, you can ensure that your doctests are comprehensive and test all relevant inputs and outputs.
- Test in big projects: For big projects, it’s particularly essential to use doctests as it becomes harder to keep track of expected output without such a testing library.
- Avoid internal tests: Avoid writing doctests that test anything that isn’t part of the interface of the function.
- Keep it simple: Do not write complex test cases in doctests. For more complex testing scenarios, prefer setup and test functions that doctest doesn’t provide.
6. Conclusion
Doctests provide an easy way to document code while also testing it.
They’re easy to write and maintain and, when used correctly, can help ensure code reliability. By following guidelines like testing expected output, using descriptive docstrings, testing iteratively, and avoiding internal tests, you can write effective doctests that provide value in your codebase.
In summary, the use of doctests can help improve code quality, readability, and reliability, and should be included in any Python developer’s toolkit. In conclusion, doctests are an essential tool in Python for testing code while also documenting it.
The advantages of using doctests include easy maintenance, code reliability, and increased readability. When writing effective doctests, it is necessary to test expected outputs, use descriptive docstrings, and test iteratively.
Doctests are particularly beneficial for big projects, as failure to track expected output can cause problems. By incorporating doctests into your development process, you can ensure code reliability, reduce errors, and improve code quality.