Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
Test Driven Python Development

You're reading from   Test Driven Python Development Develop high-quality and maintainable Python applications using the principles of test-driven development

Arrow left icon
Product type Paperback
Published in Apr 2015
Publisher Packt
ISBN-13 9781783987924
Length 264 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Siddharta Govindaraj Siddharta Govindaraj
Author Profile Icon Siddharta Govindaraj
Siddharta Govindaraj
Arrow right icon
View More author details
Toc

Table of Contents (20) Chapters Close

Test-Driven Python Development
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
1. Getting Started with Test-Driven Development FREE CHAPTER 2. Red-Green-Refactor – The TDD Cycle 3. Code Smells and Refactoring 4. Using Mock Objects to Test Interactions 5. Working with Legacy Code 6. Maintaining Your Test Suite 7. Executable Documentation with doctest 8. Extending unittest with nose2 9. Unit Testing Patterns 10. Tools to Improve Test-Driven Development Answers to Exercises Working with Older Python Versions Index

Red-Green-Refactor – The TDD Cycle


This exercise asks us to add support for updates which come out of order, that is, a newer update is followed by an older update. We need to use the timestamp to determine which update is newer and which is older.

The following is a test case for this requirement:

def test_price_is_the_latest_even_if_updates_are_made_out_of_order(self):
    self.goog.update(datetime(2014, 2, 13), price=8)
    self.goog.update(datetime(2014, 2, 12), price=10)
    self.assertEqual(8, self.goog.price)

In the test above, we first give the update for February 13, followed by the update for February 12. We then assert that the price attribute returns the latest price (for February 13). The test fails of course.

In order to make this test pass, we can't simply add the latest update to the end of the price_history list. We need to check the timestamp and insert it accordingly into the list, keeping it sorted by timestamp.

The bisect module provided in the Python standard library contains the insort_left function that inserts into a sorted list. We can use this function as follows (remember to import bisect at the top of the file):

def update(self, timestamp, price):
    if price < 0:
        raise ValueError("price should not be negative")
    bisect.insort_left(self.price_history, (timestamp, price))

In order to have a sorted list, the price_history list needs to keep a list of tuples, with the timestamp as the first element. This will keep the list sorted by the timestamp. When we make this change, it breaks our other methods that expect the list to contain the price alone. We need to modify them as follows:

@property
def price(self):
    return self.price_history[-1][1] \
        if self.price_history else None

def is_increasing_trend(self):
    return self.price_history[-3][1] < \
        self.price_history[-2][1] < self.price_history[-1][1]

With the above changes, all our existing tests as well as the new test start passing.

Now that we have the tests passing, we can look at refactoring the code to make it easier to read. Since the price_history list now contains tuples, we have to refer to the price element by tuple index, leading to statements list price_history[-1][1], which are not very clear. We can make this clearer by using a named tuple that allows us to assign names to the tuple values. Our refactored Stock class now looks like the following:

PriceEvent = collections.namedtuple("PriceEvent", ["timestamp", "price"])

class Stock:
    def __init__(self, symbol):
        self.symbol = symbol
        self.price_history = []

    @property
    def price(self):
        return self.price_history[-1].price \
            if self.price_history else None

    def update(self, timestamp, price):
        if price < 0:
            raise ValueError("price should not be negative")
        bisect.insort_left(self.price_history, PriceEvent(timestamp, price))

    def is_increasing_trend(self):
        return self.price_history[-3].price < \
            self.price_history[-2].price < \
                self.price_history[-1].price

After the change, we run the tests to ensure that everything still works.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at £13.99/month. Cancel anytime
Visually different images