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
Our file layout should now be like the following:
src | +- stock_alerter | +- __init__.py +- stock.py +- tests +- __init__.py +- test_stock.py
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:
python.exe -m unittest
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:
-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
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 .
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_stockwill run the tests only in
You can further refine to a specific class or method, such as
python3 -m unittest stock_alerter.tests.test_stock.StockTest.