Unit Testing
Using the unittest module to write Python tests
Structure
We use python's built-in vanilla unittest module to write tests. For most use cases, this package provides us with sufficient tools to get the job done
Example unit test folder structure
/src
/packageA
- A.py
/tests
/packageA
- test_A.py
run_tests.py
In general, we want the directory that holds the tests to be of the same structure as the packages and modules we intend to test (i.e packageA).
TestCase
The smallest unit of testing is an instance of unittest.TestCase. This represents a single test case/or a few closely related ones.
from packageA.A import A
import unittest
class TestA(unittest.TestCase):
def setUp(self):
... # Code runs before each test_* is executed
def test_A_like_this(self):
... # Write asserts here
def test_A_like_this_too(self):
... # Write asserts here too
def tearDown(self):
... # Code runs after each test_* is executed
if __name__ == 'main':
# Runs all test cases within this module
unittest.main()
Some pointers:
setUp & tearDown are called before & after each test method is called. (Called each time for each test_* method)
test methods must be of the form test_* to be run by unittest
A unittest.TestCase can also implement runTest as opposed to test_* methods if it is only implementing one test
Discovery (TestLoader)
We can group multiple TestCases together into TestSuites and run them (This is usually what we want to do). We want to write unittests without having to worry to import and run them as well. TestLoaders make this very easy.
The unittest.TestLoader has a discover method that auto-detects TestCases within a directory and adds them all to a suite.
Let's assume that our folder structure looks like this:
/src
/packageA
- A.py
/tests
- __init__.py
/packageA
- __init__.py
- test_A.py
run_tests.py
We can load and run all tests within the /tests directory from run_tests.py in the following manner:
import unittest
# Provide top_level_dir otherwise unittest changes sys.path messing up imports
suite_with_all_tests = unittest.defaultTestLoader.discover('./tests', top_level_dir='.')
runner = unittest.TextTestLoader()
runner.run(suite_with_all_tests)
Some pointers about TestLoader:
If you want to define your own way to search for test_cases you can make your own TestLoader that overrides discover
discover() under the hood looks for load_tests to be defined within __init__ files or the modules. More here: https://docs.python.org/2/library/unittest.html#test-discovery
modules that house Test_Cases must be named test_*.py
Directories & subdirs to be searched must have __init__.py files. discover seems to not work well with Implicit Namespace Packages. See this: https://stackoverflow.com/questions/46976256/recursive-unittest-discovery-with-python3-and-without-init-py-files
top_level_dir should be passed in as '.'. Otherwise it sets the start_dir as the top_level_dir, adds this to sys.path and can mess up imports across the app
Last updated
Was this helpful?