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.

test_A.py
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:

run_tests.py
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:

Last updated

Was this helpful?