Writing a testable novel with doctest

Running a series of story tests showcases your code's expected behavior. We have previously seen in the Writing a testable story with doctest recipe how to build a testable story and have it generate a useful report.

With this recipe, we will see how to use this tactic to string together multiple testable stories to form a testable novel.

Getting ready

For this recipe, we will be using the shopping cart application shown at the beginning of this chapter.

We will also re-use the BddDocTestRunner defined in this chapter's Writing a testable story with doctest recipe. But we will slightly alter it in the following steps.

How to do it...

With the following steps,we will look at how to write a testable novel:

  1. Create a new file called recipe29.py.
  2. Copy the code containing the BddDocTestRunner from the Writing a testable story with doctest recipe into recipe29.py.
  3. Alter the __main__ runnable to only search for this recipe's doctest scenarios.
    if __name__ == "__main__":
        from glob import glob
     
        doctest.DocTestRunner = BddDocTestRunner
     
        for file in glob("recipe29*.doctest"):
            given = file[len("recipe29_"):]
            given = given[:-len(".doctest")]
            given = " ".join(given.split("_"))
            print "==================================="
            print "Given a %s..." % given
            print "==================================="
            doctest.testfile(file)
  4. Create a new file called recipe29_cart_we_will_load_with_identical_items.doctest.
  5. Add a scenario to it that tests the cart by adding two instances of the same object.
    >>> from cart import *
     
    >>> cart = ShoppingCart()
     
    #when we add an item
    >>> cart.add("carton of milk", 2.50) #doctest:+ELLIPSIS
    <cart.ShoppingCart object at ...>
     
    #the first item is a carton of milk
    >>> cart.item(1)
    'carton of milk'
     
    #the first price is $2.50
    >>> cart.price(1)
    2.5
     
    #there is only one item
    >>> len(cart)
    1
     
    This shopping cart let's us grab more than one
    of a particular item.
    #when we add a second carton of milk
    >>> cart.add("carton of milk", 2.50) #doctest:+ELLIPSIS
    <cart.ShoppingCart object at ...>
     
    #the first item is still a carton of milk
    >>> cart.item(1)
    'carton of milk'
     
    #but the price is now $5.00
    >>> cart.price(1)
    5.0
     
    #and the cart now has 2 items
    >>> len(cart)
    2
     
    #for a total (with 10% taxes) of $5.50
    >>> cart.total(10.0)
    5.5
  6. Create another file called recipe29_cart_we_will_load_with_two_different_items.docstest.
  7. In that file, create another scenario that tests the cart by adding two different instances.
    >>> from cart import *
     
    >>> cart = ShoppingCart()
     
    #when we add a carton of milk...
    >>> cart.add("carton of milk", 2.50) #doctest:+ELLIPSIS
    <cart.ShoppingCart object at ...>
     
    #when we add a frozen pizza...
    >>> cart.add("frozen pizza", 3.00) #doctest:+ELLIPSIS
    <cart.ShoppingCart object at ...>
     
    #the first item is the carton of milk
    >>> cart.item(1)
    'carton of milk'
     
    #the second item is the frozen pizza
    >>> cart.item(2)
    'frozen pizza'
     
    #the first price is $2.50
    >>> cart.price(1)
    2.5
     
    #the second price is $3.00
    >>> cart.price(2)
    3.0
     
    #the total with no tax is $5.50
    >>> cart.total(0.0)
    5.5
     
    #the total with 10% tax is $6.05
    >>> print round(cart.total(10.0), 2)
    6.05
  8. Create a new file called recipe29_cart_that_we_intend_to_keep_empty.doctest.
  9. In that file, create a third scenario that tests the cart by adding nothing and yet tries to access values outside the range.
    >>> from cart import *
     
    #when we create an empty shopping cart
    >>> cart = ShoppingCart()
     
    #accessing an item out of range generates an exception
    >>> cart.item(5)
    Traceback (most recent call last):
    ...
    IndexError: list index out of range
     
    #accessing a price with a negative index causes an exception
    >>> cart.price(-2)
    Traceback (most recent call last):
    ...
    IndexError: list index out of range
     
    #calculating a price with no tax results in $0.00
    >>> cart.total(0.0)
    0.0
     
    #calculating a price with a tax results in $0.00
    >>> cart.total(10.0)
    0.0
  10. Use the runner to execute our scenarios.
    How to do it...

How it works...

We reuse the test runner developed in the previous recipe. The key is extending the scenarios to ensure that we have complete coverage of the expected scenarios.

We need to be sure that we can handle:

  • A cart with two identical items
  • A cart with two different items
  • The degenerate situation of an empty shopping cart

There's more...

A valuable part of writing tests is picking useful names. In our situation, each testable story started with an empty cart. However, if we named each scenario 'given an empty cart' would cause an overlap and not result in a very effective report.

So instead, we named them based on our story's intention:

recipe29_cart_we_will_load_with_identical_items.doctest
recipe29_cart_we_will_load_with_two_different_items.doctest
recipe29_cart_that_we_intend_to_keep_empty.doctest

This leads to:

  • Given a cart we will load with identical items
  • Given a cart we will load with two different items
  • Given a cart that we intend to keep empty

The purpose of these scenarios is much clearer.

Naming scenarios are much like certain aspects of software development that are more a craft than a science. Tuning the performance tends to be more scientific, because it involves an iterative process of measurement and adjustment. But naming scenarios along with their causes and effects tends to be more of a craft. It involves communicating with all the stakeholders including QA and customers, so everyone can read and understand the stories.

Tip

Don't be intimidated. Be ready to embrace change

Start writing your stories. Make them work. Then share them with your stakeholders. Feedback is important, and that is the purpose of using story-based testing.

Be ready for criticism and suggested changes. Be ready for more story requests. In fact, don't be surprised if some of your customers or QA want to write their own stories. That is a positive sign.

If you are new to this type of customer interaction, don't worry. You will develop valuable communication skills and build a solid professional relationship with your stakeholders. And at the same time, your code quality will certainly improve.

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

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