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
Explore 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 (14) Chapters Close

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 A. Answers to Exercises B. Working with Older Python Versions Index

Using TDD to build a stock alert application

Over the course of this book, we are going to be using TDD to build a simple stock alert application. The application will listen to stock updates from a source. The source can be anything—a server on the Internet, or a file on the hard drive, or something else. We will be able to define rules, and when the rule is matched, the application sends us an email or text message.

For example, we could define a rule as "If AAPL crosses the $550 level then send me an email". Once defined, the application will monitor updates and send an e-mail when the rule is matched.

Writing our first test

Enough talk. Let's get started with our application. What is a good place to start? From examining the application description mentioned earlier, it looks like we will need the following modules:

  • Some way to read stock price updates, either from the Internet or from a file
  • A way to manage the stock information so that we can process it
  • A way to define rules and match them against the current stock information
  • A way to send an email or text message when a rule is matched

Based on these requirements, we will be using the following design:

Writing our first test

Each term is discussed as follows:

  • Alert: This is the core of the application. An alert will take a Rule and map it to an Action. When the rule is matched, the action is executed.
  • Rule: A Rule contains the condition we want to check for. We should get alerted when the rule is matched.
  • Action: This is the action to be performed when the rule is matched. This could be as simple as printing a message on the screen, or, in more real-work scenarios, we might send an e-mail or a text message.
  • Stock: The Stock class keeps track of the current price and possibly a history of the prices for a stock. It sends an Event to the Alert when there is an update. The alert then checks if it's rule matched and whether any action needs to be executed.
  • Event: This class is used to send events to the Alert when a Stock is updated.
  • Processor: The processor takes stock updates from the Reader and updates the Stock with the latest data. Updating the stock causes the event to be fired, which, in turn, causes the alert to check for a rule match.
  • Reader: The Reader gets the stock alerts from some source. In this book, we are going to get updates from a simple list or a file, but you can build other readers to get updates from the Internet or elsewhere.

Among all these classes, the way to manage stock information seems to be the simplest, so let's start there. What we are going to do is to create a Stock class. This class will hold information about the current stock. It will store the current price and possibly some recent price history. We can then use this class when we want to match rules later on.

To get started, create a directory called src. This directory is going to hold all our source code. In the rest of this book, we will refer to this directory as the project root. Inside the src directory, create a subdirectory called stock_alerter. This is the directory in which we are going to implement our stock alert module.

Okay, let's get started with implementing the class.

NO! Wait! Remember the TDD process that was described earlier? The first step is to write a test, before we code the implementation. By writing the test first, we now have the opportunity to think about what we want this class to do.

So what exactly do we want this class to do? Let's start with something simple:

  • A Stock class should be instantiated with the ticker symbol
  • Once instantiated, and before any updates, the price should be None

Of course, there are many more things we will want this class to do, but we'll think about them later. Rather than coming up with a very comprehensive list of functionality, we're going to focus on tiny bits of functionality, one at a time. For now, the preceding expectation is good enough.

To convert the preceding expectation into code, create a file called stock.py in the project root, and put the following code in it:

import unittest
class StockTest(unittest.TestCase):
    def test_price_of_a_new_stock_class_should_be_None(self):
        stock = Stock("GOOG")
        self.assertIsNone(stock.price)
if __name__ == "__main__":
    unittest.main()

What does this code do?

  1. First, we import unittest. This is the library that has the test framework that we are going to use. Luckily for us, it is bundled into the Python standard library by default and is always available, so we don't need to install anything, we can just import the module directly.
  2. Second, we create a class StockTest. This class will hold all the test cases for the Stock class. This is just a convenient way of grouping related tests together. There is no rule that every class should have a corresponding test class. Sometimes, if we have a lot of tests for a class, then we may want to create separate test classes for each individual behavior, or group the tests some other way. However, in most cases, creating one test class for an actual class is the best way to go about it.
  3. Our StockTest class inherits from the TestCase class in the unittest module. All tests need to inherit from this class in order to be identified as a test class.
  4. Inside the class, we have one method. This method is a test case. The unittest framework will pick up any method that starts with test. The method has a name that describes what the test is checking for. This is just so that when we come back after a few months, we still remember what the test does.
  5. The test creates a Stock object and then checks if the price is None. assertIsNone is a method provided by the TestCase class that we are inheriting from. It checks that its parameter is None. If the parameter is not None, it raises an AssertionError and fails the test. Otherwise, execution continues to the next line. Since that is the last line of the method, the test completes and is marked as a pass.
  6. The last segment checks if the module was executed directly from the command line. In such a case, the __name__ variable will have the value __main__, and the code will execute the unittest.main() function. This function will scan the current file for all tests and execute them. The reason we need to wrap this function call inside the conditional is because this part does not get executed if the module is imported into another file.

Congratulations! You have your first failing test. Normally, a failing test would be a cause for worry, but in this case, a failing test means that we're done with the first step of the process and can move on to the next step.

Analyzing the test output

Now that we've written our test, it is time to run it. To run the test, just execute the file. Assuming that the current directory is the src directory, the following is the command to execute the file:

  • Windows:
    python.exe stock_alerter\stock.py
    
  • Linux/Mac:
    python3 stock_alerter/stock.py
    

If the python executable is not on your path, then you will have to give the full path to the executable here. In some Linux distributions, the file may be called python34 or python3.4 instead of python3.

When we run the file, the output looks like the following:

E
=====================================================================
ERROR: test_price_of_a_new_stock_class_should_be_None (__main__.StockTest)
---------------------------------------------------------------------
Traceback (most recent call last):
  File "stock_alerter\stock.py", line 6, in test_price_of_a_new_stock_class_should_be_None
    stock = Stock("GOOG")
NameError: name 'Stock' is not defined
---------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

As expected, the test fails, because we haven't created the Stock class yet.

Let's look at that output in a little more detail:

  • E on the first line signifies that the test gave an error. If a test passed, then you would have a dot on that line. A failed test would be marked with F. Since we have only a single test, there is only one character there. When we have multiple tests, then the status of each test will be displayed on that line, one character per test.
  • After all the test statuses are displayed, we get a more detailed explanation of any test errors and failures. It tells us whether there was a failure or an error (in this case denoted by ERROR) followed by the name of the test and which class it belongs to. This is followed by a traceback, so we know where the failure occurred.
  • Finally, there is a summary that shows how many tests were executed, how many passed or failed, and how many gave errors.

Test errors versus test failures

There are two reasons why a test might not pass: It might have failed or it might have caused an error. There is a small difference between these two. A failure indicates that we expected some outcome (usually via an assert), but got something else. For example, in our test, we are asserting that stock.price is None. Suppose stock.price has some other value apart from None, then the test will fail.

An error indicates that something unexpected happened, usually an unexpected exception was raised. In our previous example, we got an error because the Stock class has not yet been defined.

In both the cases, the test does not pass, but for different reasons, and these are reported separately as test failures and test errors.

Making the test pass

Now that we have a failing test, let's make it pass. Add the following code to the stock.py file, after the import unittest line:

class Stock:
    def __init__(self, symbol):
        self.symbol = symbol
        self.price = None

What we have done here is to implement just enough code to pass the test. We've created the Stock class so the test shouldn't complain about it being missing, and we've initialized the price attribute to None.

What about the rest of the implementation for this class? This can wait. Our main focus right now is to pass the current expectation for this class. As we write more tests, we will end up implementing more of the class as well.

Run the file again, and this time the output should be like the following:

.
---------------------------------------------------------------------
Ran 1 test in 0.000s

OK

We've got a dot in the first line, which signifies that the test is passing. The OK message at the end tells us that all tests have passed.

The final step is to refactor the code. With so little code, there is really nothing much to clean up. So, we can skip the refactoring step and start with the next test.

You have been reading a chapter from
Test Driven Python Development
Published in: Apr 2015
Publisher: Packt
ISBN-13: 9781783987924
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 ₹800/month. Cancel anytime