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.
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.
With the following steps,we will look at how to write a testable novel:
recipe29.py
.BddDocTestRunner
from the Writing a testable story with doctest recipe into recipe29.py
.__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)
recipe29_cart_we_will_load_with_identical_items.doctest
.>>> 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
recipe29_cart_we_will_load_with_two_different_items.docstest
.>>> 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
recipe29_cart_that_we_intend_to_keep_empty.doctest
.>>> 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
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 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:
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.
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.
3.145.39.60