Book Image

Test-Driven Python Development

By : Siddharta Govindaraj
Book Image

Test-Driven Python Development

By: Siddharta Govindaraj

Overview of this book

This book starts with a look at the test-driven development process, and how it is different from the traditional way of writing code. All the concepts are presented in the context of a real application that is developed in a step-by-step manner over the course of the book. While exploring the common types of smelly code, we will go back into our example project and clean up the smells that we find. Additionally, we will use mocking to implement the parts of our example project that depend on other systems. Towards the end of the book, we'll take a look at the most common patterns and anti-patterns associated with test-driven development, including integration of test results into the development process.
Table of Contents (20 chapters)
Test-Driven Python Development
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Index

Reorganizing the test code


We've added the test cases in the same file as the code. This is a good, simple way to add test cases to standalone scripts and applications that are not too complex. However, for larger applications, it is a good idea to keep test code separate from production code.

There are two common patterns for organizing test code this way.

The first pattern is to keep test code in a separate root directory, as shown in the following:

root
|
+- package
|  |
|  +- file1
|  +- file2
|
+- test
   |
   +- test_file1
   +- test_file2

The other pattern is to keep test code as a submodule of the main code, as shown in the following:

root
|
+- package
   |
   +- file1
   +- file2
   +- test
      |
      +- test_file1
      +- test_file2

The first pattern is commonly used for standalone modules as it allows us to distribute the code and tests together. Tests can generally be run without having to perform a lot of setup or configuration. The second pattern has an advantage when the application has to be packaged without the test code, for example when deploying to production servers, or distributing to customers (in the case of a commercial application). However, both the patterns are in popular use, and it is mainly a personal preference as to which method to use.

We are going to follow the first pattern in this book. To get started, create a directory called tests inside the stock_alerter directory. Next, create a file called test_stock.py in this directory. We will put all our test cases in one-to-one correspondence with the source file. This means, a file called sample.py will have its test cases in the tests/test_sample.py file. This is a simple naming convention that helps to quickly locate test cases.

Finally, we move our test cases into this file. We also need to import the Stock class to be able to use it in the test case. Our test_stock.py file now looks like the following:

import unittest
from ..stock import Stock

class StockTest(unittest.TestCase):
    def test_price_of_a_new_stock_class_should_be_None(self):
        stock = Stock("GOOG")
        self.assertIsNone(stock.price)

Remember to remove the import unittest line from stock.py, now that it no longer contains the test code. Previously we had just one standalone script, but we now have a stock_alerter module and a stock_alerter.tests submodule. Since we are now working with modules, we should also add in an empty __init__.py file in both the stock_alerter and tests directories.

Our file layout should now be like the following:

src
|
+- stock_alerter
   |
   +- __init__.py
   +- stock.py
   +- tests
      +- __init__.py
      +- test_stock.py

Running the tests after the reorganization

If you have noticed, we no longer have a call to unittest.main() in the test code. Including a call to unittest.main() works well with individual scripts since it allows us to run the tests by simply executing the file. However, it is not a very scalable solution. If we have hundreds of files, we would like to run all the tests at once, and not have to execute each file individually.

To address this, Python 3 comes with a very nice test discovery and execution capability from the command line. Simply go into the src directory and run the following command:

  • Windows:

    python.exe -m unittest
    
  • Linux/Mac:

    python3 -m unittest
    

This command will go through the current directory and all subdirectories and run all the tests that are found. This is the default autodiscover mode of execution, where the command searches all the files and runs the tests. Autodiscovery can also be explicitly run with the following command:

python3 -m unittest discover

Autodiscover can be customized to check in specific directories or files with the following parameters:

  • -s start_directory: Specify the start directory from where the discovery should start. This defaults to the current directory.

  • -t top_directory: Specify the top-level directory. This is the directory from which imports are performed. This is important if the start directory is inside the package and you get errors due to incorrect imports. This defaults to the start directory.

  • -p file_pattern: The file pattern that identifies test files. By default it checks for python files that start with test. If we name our test files something else (for example, stock_test.py), then we have to pass in this parameter so that the file is correctly identified as a test file.

To illustrate the difference between the start and top directory, run the following command from the src directory:

python3 -m unittest discover -s stock_alerter

The preceding command will fail with an import error. The reason is because when the start directory is set to stock_alerter, then the tests directory is imported as a top-level module, and the relative import fails. To get around this, we need to use the following command:

python3 -m unittest discover -s stock_alerter -t .

This command will import all modules relative to the top directory, and so stock_alerter correctly becomes the main module.

You can also disable autodiscovery and specify only certain tests to be run:

  • Passing in a module name will only run the tests within that module. For example, python3 -m unittest stock_alerter.tests.test_stock will run the tests only in test_stock.py.

  • You can further refine to a specific class or method, such as python3 -m unittest stock_alerter.tests.test_stock.StockTest.