Coding a test harness for doctest

The tests we have written so far are very simple, because the function we are testing is simple. There are two inputs and one output with no side effects. No objects have to be created. This isn't the most common use case for us. Often, we have objects that interact with other objects.

The doctest module supports creating objects, invoking methods, and checking results. With this recipe, we will explore this in more detail.

An important aspect of doctest is that it finds individual instances of docstrings, and runs them in a local context. Variables declared in one docstring cannot be used in another docstring.

How to do it...

  1. Create a new file called recipe19.py to contain the code from this recipe.
  2. Write a simple shopping cart application.
    class ShoppingCart(object):
        def __init__(self):
            self.items = []
    
        def add(self, item, price):
            self.items.append(Item(item, price))
            return self
    
        def item(self, index):
            return self.items[index-1].item
    
        def price(self, index):
            return self.items[index-1].price
    
        def total(self, sales_tax):
            sum_price = sum([item.price for item in self.items])
            return sum_price*(1.0 + sales_tax/100.0)
    
        def __len__(self):
            return len(self.items)
    
    class Item(object):
        def __init__(self, item, price):
            self.item = item
            self.price = price
  3. Insert a docstring at the top of the module, before the ShoppingCart class declaration.
    """
    This is documentation for the this entire recipe.
    With it, we can demonstrate usage of the code.
    
    >>> cart = ShoppingCart().add("tuna sandwich", 15.0)
    >>> len(cart)
    1
    >>> cart.item(1)
    'tuna sandwich'
    >>> cart.price(1)
    
    15.0
    >>> print round(cart.total(9.25), 2)
    
    16.39
    """
    class ShoppingCart(object):
    ...
  4. Run the recipe using -m doctest and -v for verbosity.
    How to do it...
  5. Copy all the code we just wrote from recipe19.py into a new file called recipe19b.py.
  6. Inside recipe19b.py add another docstring to item, which attempts to re-use the cart variable defined at the top of the module.
        def item(self, index):
            """
            >>> cart.item(1)
            'tuna sandwich'
            """
            return self.items[index-1].item
    
  7. Run this variant of the recipe. Why does it fail? Wasn›t cart declared in the earlier docstring?
    How to do it...

How it works...

The doctest module looks for every docstring. For each docstring it finds, it creates a shallow copy of the module's global variables and then runs the code and checks results. Apart from that, every variable created is locally scoped and then cleaned up when the test is complete. This means that our second docstring that was added later cannot see the cart that was created in our first docstring. That is why the second run failed.

There is no equivalent to a setUp method as we used with some of the unittest recipes. If there is no setUp option with doctest, then what value is this recipe? It highlights a key limitation of doctest that all developers must understand before using it.

There's more...

The doctest module provides an incredibly convenient way to add testability to our documentation. But this is not a substitute for a full-fledged testing framework, like unittest. As noted earlier, there is no equivalent to a setUp. There is also no syntax checking of the Python code embedded in the docstrings.

Mixing the right level of doctests with unittest (or other testing framework we pick) is a matter of judgment.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.197.251