Unit tests with Python

Posted on December 13, 2021 by Adrian Wyssmann ‐ 3 min read

In my previous post I gave an introduction to unit testing with Java. As personally I use Python, hence I give you a quick intro into unit testing with Python

As we know now, we need a unit test framework to do unit testing. If we have a look at Python, it comes with a unittest framework included:

The unittest unit testing framework was originally inspired by JUnit and has a similar flavor as major unit testing frameworks in other languages.

It offers the concepts I discussed here:

  • test fixture - well, provide text fixture
  • test case - the individual unit of testing
  • test suite - a collection of test cases, test suites, or both
  • test runner - the component which orchestrates the execution of tests and provides the outcome to the user

Let’s say we have a script example.py with the following content:

def simpleFunction(x,y):
    if  (5 <= x and y < 10):
        return "do this"
    else:
        return "do that"

We would create a test case which tests the different path of simpleFunction. We call the file test_example.py as follows

from example import *
import unittest

class SimpleFunctionTest(unittest.TestCase):
    def test_dothistest(self):
        self.assertAlmostEqual(simpleFunction(6,2), 'do this')
        self.assertAlmostEqual(simpleFunction(5,9), 'do this')
    def test_dothattest(self):
        self.assertAlmostEqual(simpleFunction(5,10), 'do that')
        self.assertAlmostEqual(simpleFunction(1,9), 'do that')

if __name__ == '__main__':
    unittest.main()

Some things which you need to know

  • a test class is usually test_classname.py - don’t use a . i.e classname.test.py as this will result in an error when executing the tests
  • it must import unittest as well as test object - in this case example
  • a test classis subclassing unittest.TestCase.
  • the name of the test class does not matter, but the test functions have to be prefixed with test_ - otherwise the functions will not be considered test cases
  • call unittest.main() when __name__ == '__main__ - this provides a command-line interface to the test script

You can call now the unit tests from the command line - we use -v to see the name of the test cases:

$ python3 example_test.py -v
test_dothattest (example_test.SimpleFunctionTest) ... ok
test_dothistest (example_test.SimpleFunctionTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

If you have multiple files, you can also test discovery, which automatically detects tests.

$ python -m unittest -v
test_dothattest (test_example.SimpleFunctionTest) ... ok
test_dothistest (test_example.SimpleFunctionTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Or

python -m unittest discover -v
test_dothattest (example_test.SimpleFunctionTest) ... ok
test_dothistest (example_test.SimpleFunctionTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

It also offers setUp() and tearDown() methods, which will be executed before and after each test method.

You also can skip tests using the skip()-decorator or raise skipTest. Here an example of the official documentation:

class MyTestCase(unittest.TestCase):

    @unittest.skip("demonstrating skipping")
    def test_nothing(self):
        self.fail("shouldn't happen")

    @unittest.skipIf(mylib.__version__ < (1, 3),
                     "not supported in this library version")
    def test_format(self):
        # Tests that work for only a certain version of the library.
        pass

    @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
    def test_windows_support(self):
        # windows specific testing code
        pass

    def test_maybe_skipped(self):
        if not external_resource_available():
            self.skipTest("external resource not available")
        # test code that depends on the external resource
        pass

If you want to group test cases together, you can build TestSuites

import unittest
from example_test import *

def suite():
    suite = unittest.TestSuite()
    suite.addTest(SimpleFunctionTest('test_dothistest'))
    suite.addTest(SimpleFunctionTest('test_dothattest'))
    return suite

if __name__ == '__main__':
    runner = unittest.TextTestRunner()
    runner.run(suite())

You can run this as follows:

python example_suite_test.py -v
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Another interesting feature is SubTests, which allows you to distinguish small differences for some parameters, using the same test method.

I recommend to read organize test code to better understand how you should organize your test code.

When writing code, it’s important you understand unit testing and the framework, the language of your choice offers. Whether it’s unittest for Python or JUnit for Java, they support the same important concepts, even so they differ in syntax.