10.14 Unit Testing with Docstrings and doctest

For your convenience, this notebook includes the entire contents of accountdoctest.py so you can modify it and rerun the tests. When you execute the cell containing accountdoctest.py, the doctests will execute.

  • A key aspect of software development is testing your code to ensure that it works correctly
  • Even with extensive testing, code may still contain bugs
  • According to the famous Dutch computer scientist Edsger Dijkstra, “Testing shows the presence, not the absence of bugs.”

Module doctest and the testmod Function

  • doctest module helps you test your code and conveniently retest it after you make modifications
  • When you execute the doctest module’s testmod function, it inspects your functions’, methods’ and classes' docstrings looking for sample Python statements preceded by >>>, each followed on the next line by the given statement’s expected output (if any)
  • testmod executes those statements and confirms that they produce the expected output
  • If not, testmod reports errors indicating which tests failed so you can locate and fix the problems in your code
  • Each test you define in a docstring tests a specific unit of code, such as a function, a method or a class
  • Such tests are called unit tests

Modified Account Class

The file accountdoctest.py contains the class Account from this chapter’s first example

  • Modified __init__’s docstring to include four tests which can be used to ensure that the method works correctly:
    • First test creates a sample Account object named account1
      • This statement does not produce any output
    • Second test shows what the value of account1’s name attribute should be if the first test executed successfully
    • Third test shows what the value of account1’s balance attribute should be if the first test executed successfully
    • The last test creates an Account object with an invalid initial balance
      • Sample output shows that a ValueError exception should occur in this case
      • For exceptions, the doctest module’s documentation recommends showing just the first and last lines of the traceback
  • You can intersperse your tests with descriptive text
In [1]:
# accountdoctest.py
"""Account class definition."""
from decimal import Decimal

class Account:
    """Account class for demonstrating doctest."""
    
    def __init__(self, name, balance):
        """Initialize an Account object.
        
        >>> account1 = Account('John Green', Decimal('50.00'))
        >>> account1.name
        'John Green'
        >>> account1.balance
        Decimal('50.00')

        The balance argument must be greater than or equal to 0.
        >>> account2 = Account('John Green', Decimal('-50.00'))
        Traceback (most recent call last):
            ...
        ValueError: Initial balance must be >= to 0.00.
        """

        # if balance is less than 0.00, raise an exception
        if balance < Decimal('0.00'):
            raise ValueError('Initial balance must be >= to 0.00.')

        self.name = name
        self.balance = balance

    def deposit(self, amount):
        """Deposit money to the account."""

        # if amount is less than 0.00, raise an exception
        if amount < Decimal('0.00'):
            raise ValueError('amount must be positive.')

        self.balance += amount

if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)
Trying:
    account1 = Account('John Green', Decimal('50.00'))
Expecting nothing
ok
Trying:
    account1.name
Expecting:
    'John Green'
ok
Trying:
    account1.balance
Expecting:
    Decimal('50.00')
ok
Trying:
    account2 = Account('John Green', Decimal('-50.00'))
Expecting:
    Traceback (most recent call last):
        ...
    ValueError: Initial balance must be >= to 0.00.
ok
3 items had no tests:
    __main__
    __main__.Account
    __main__.Account.deposit
1 items passed all tests:
   4 tests in __main__.Account.__init__
4 tests in 4 items.
4 passed and 0 failed.
Test passed.

Module __main__

  • When you load any module, Python assigns a string containing the module’s name to a global attribute of the module called __name__
  • When you execute a Python source file as a script, Python uses the string '__main__' as the module’s name
  • Can use __name__ in an if statement to specify code that should execute only if the source file is executed as a script
    • We import the doctest module and call the module’s testmod function to execute the docstring unit tests

Running Tests

  • Run the file accountdoctest.py as a script to execute the tests
  • If you call testmod with no arguments, it does not show test results for successful tests
  • This example calls testmod with the keyword argument verbose=True, which shows every test’s results
  • To demonstrate a failed test, “comment out” lines 25–26 in accountdoctest.py by preceding each with a #, then run accountdoctest.py as a script

IPython %doctest_mode Magic

  • A convenient way to create doctests for existing code is to use an IPython interactive session to test your code, then copy and paste that session into a docstring
  • IPython’s In [] and Out[] prompts are not compatible with doctest, so IPython provides the magic %doctest_mode to display prompts in the correct doctest format
    • Toggles between the two prompt styles

©1992–2020 by Pearson Education, Inc. All Rights Reserved. This content is based on Chapter 5 of the book Intro to Python for Computer Science and Data Science: Learning to Program with AI, Big Data and the Cloud.

DISCLAIMER: The authors and publisher of this book have used their best efforts in preparing the book. These efforts include the development, research, and testing of the theories and programs to determine their effectiveness. The authors and publisher make no warranty of any kind, expressed or implied, with regard to these programs or to the documentation contained in these books. The authors and publisher shall not be liable in any event for incidental or consequential damages in connection with, or arising out of, the furnishing, performance, or use of these programs.