Advanced API for doctest

Let’s talk about how to use Python’s built-in testing framework called doctest. Doctests are great for finding examples in docstrings and running them to make sure they work as expected. But what if you want to take things up a notch? Let’s begin exploring with the advanced features of doctest!

First off, how to use doctests with functions that have multiple input parameters or return values. For example:

# Define a function called calculate_sum that takes in two parameters, a and b
def calculate_sum(a, b):
    """Calculate the sum of two numbers and print it out."""
    # Create a variable called result and assign it the value of a + b
    result = a + b
    # Print out a string with the sum of a and b
    print("The sum is:", result)
    # Return the value of result
    return result

To test this function with doctest, you can add docstrings that include examples:

# Define a function called calculate_sum that takes in two parameters, a and b
def calculate_sum(a, b):
    """Calculate the sum of two numbers and print it out.

    > calculate_sum(2, 3) # Example of how to use the function with expected output
    The sum is: 5
    
    > result = calculate_sum(10, 7) # Assigning the result of the function to a variable
    > print("The calculated sum is:", result) # Printing the result with a custom message
    """
    # Calculate the sum of a and b and assign it to a variable called sum
    sum = a + b
    # Print out the sum with a custom message
    print("The sum is:", sum)
    # Return the sum for future use
    return sum

In the first example, we’re testing that when `calculate_sum()` is called with arguments of 2 and 3, it prints out “The sum is: 5”. In the second example, we’re assigning the output to a variable named `result`, which allows us to test other aspects of our function.

But what if you have functions that return multiple values? Let’s say you have this function:

# This function calculates the average of a list of numbers and prints it out, as well as returning the average and the count of numbers in the list.

def calculate_average(numbers):
    """Calculate the average of a list of numbers and print it out."""
    total = sum(numbers) # calculates the sum of all numbers in the list
    count = len(numbers) # counts the number of elements in the list
    result = float(total) / count # calculates the average by dividing the total by the count and converting it to a float
    print("The average is:", result) # prints out the average
    return (result, count) # returns a tuple containing the average and the count of numbers in the list

To test this function with doctest, you can add docstrings that include examples:

# Define a function called calculate_average that takes in a list of numbers as a parameter
def calculate_average(numbers):
    """Calculate the average of a list of numbers and print it out.

    > result = calculate_average([10, 20, 30]) # Example of how to use the function with a list of numbers
    > print("The calculated average is:", result[0], "with", result[1], "values.") # Print out the calculated average and the number of values used to calculate it
    
    > result = calculate_average([5, 7, 9]) # Another example of how to use the function with a different list of numbers
    > print("The calculated average is:", result[0], "with", result[1], "values.") # Print out the calculated average and the number of values used to calculate it
    """
    total = 0 # Initialize a variable to keep track of the sum of all the numbers in the list
    for num in numbers: # Loop through each number in the list
        total += num # Add the current number to the total sum
    average = total / len(numbers) # Calculate the average by dividing the total sum by the number of values in the list
    return (average, len(numbers)) # Return a tuple containing the calculated average and the number of values used to calculate it

In the first example, we’re testing that when `calculate_average()` is called with arguments of [10, 20, 30], it returns a tuple containing the average and the number of values. In the second example, we’re assigning this output to a variable named `result`, which allows us to test other aspects of our function.

But what if you have functions that use complex data structures or return objects? Let’s say you have this function:

# This function creates a new person object with the given name and age.
def create_person(name, age):
    # Define a class named Person
    class Person:
        # Define the __init__ method which initializes the object with the given name and age
        def __init__(self, name, age):
            self.name = name
            self.age = age
        
        # Define a method named get_info which returns a string with the person's name and age
        def get_info(self):
            return f"{self.name} is {self.age} years old."
    
    # Create a new instance of the Person class with the given name and age
    person = Person(name, age)
    # Return the person object
    return person

To test this function with doctest, you can add docstrings that include examples:

def create_person(name, age): # Defines a function called "create_person" that takes in two parameters, "name" and "age"
    """Create a new person object with the given name and age.

    >>> john = create_person("John Doe", 30) # Creates a new person object named "john" with the name "John Doe" and age 30
    
    >>> print(john.get_info()) # Prints the information of the "john" object using the "get_info" method
    John Doe is 30 years old.
    """
    person = {} # Creates an empty dictionary to store the person's information
    person["name"] = name # Assigns the "name" parameter to the "name" key in the dictionary
    person["age"] = age # Assigns the "age" parameter to the "age" key in the dictionary
    return person # Returns the dictionary as the person object

# The following code is not necessary as it is not used in the doctest
# However, it could be used to add more functionality to the function
# For example, adding a method to get the person's information
# def get_info(self):
#     return f"{self['name']} is {self['age']} years old."

In this example, we’re testing that when `create_person()` is called with arguments of “John Doe” and 30, it returns a new object named `john`. We can then test other aspects of the returned object by calling its methods (in this case, `get_info()`) and comparing them to expected output.

And there you have it! Advanced doctesting for Python functions with multiple input parameters or return values, complex data structures, and objects.

SICORPS