BMW CarIT Logo Typo

Blog

News, ideas and events

Blog

Python unittesting, a gentle primer

Background

The Python module unittest is a built in module that comes with the standard Python installation. Its API is similar to the JUnit or CppUnit testing frameworks.
Extending the staged example from my previous blog post (http://www.bmw-carit.com/blog/two-python-modules-i-wouldnt-go-without) I would like to share with you how easy it is to do unit testing with Python.

For the rest of this blog post I assume that the simple command line tool is saved in a module named simple_tool.py.

Disclaimer

This blog post will not elaborate the Test First Approach or a Test Driven Development movement. Instead, the presented Python unittest module will have all you need to follow these movements.
Also I will not elaborate on what to test. Those topics are far beyond the scope of this blog post.

The first simple test cases

The simplest way to add unit tests to our simple tool is to add the tests directly into the module itself.
We place the tests into its own class which is a subclass of unittest.TestCase. To be recognized by the unittest framework, tests need to be placed inside methods whose name start with “test“. It is common practice to give the test methods descriptive names such as test_input_is_even_number.
A test method consists of at least of two parts: first the test set-up, secondly assertion on the results. A rule of thumb is to have only one assert per test.
In our first test we check that the outcome of the _internal_calculation method equals the expected reciprocal value of the input.

simple_tool.py containing our testcase
import logging
import argparse
import unittest
 
#code omitted
 
def _internal_calculation(number):
    #code omitted
 
class TestInternalCalculation(unittest.TestCase):
    def test_simple_number(self):
        self.assertEqual(_internal_calculation(3), 1./3)
 
if _name_ == '_main_':
    #code omitted

Placing the testcase into our module does not affect the way we run our module

Running the module with python simple_test_logging.py
Counter -> Result
-----------------
5 -> 0.200
4 -> 0.250
3 -> 0.333
2 -> 0.500
1 -> 1.000

To run the unit test inside our module we have to tell the Python interpreter to do so by calling with the parameter -m unittest:

python -m unittest simple_test_logging
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

Explaining the test output

In this basic setup every “.” denotes a test that was executed successfully (i.e. all asserts ran fine).
A failing test method will be listed with an “F” while an error (e.g. a thrown exception) will be listed with an “E”.
In our example, all tests pass. The final output displays an “OK” to confirm that all tests have succeeded together with the test run time.
To get more information about the test execution you can run the test with -v argument: python -m unittest -v simple_test_logging, then every test method run is listed by its name along with the result of this test method (ok, FAIL, ERROR)

python -m unittest -v simple_test_logging
test_simple_number (simple_test_logging.TestInternalCalculation) ... ok
----------------------------------------------------------------------
 Ran 1 test in 0.000s
OK

Putting the test in its own module

In a real world module with a decent number of functions we would like to keep the tests separated from the actual code. Placing the tests into its own module is the more accepted way.
A unit test module is like any other module, for practical reasons we include an if _name_ == "_main_" test to facilitate running the unit tests.

putting the test into its own module
import unittest
import simple_tool
 
class TestInternalCalculation(unittest.TestCase):
    def test_simple_number(self):
        self.assertEqual(simple_tool._internal_calculation(3), 1./3)
 
if _name_ == '_main_':
    unittest.main()
python test_simple_tool.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

The anatomy of a test case

setup and teardown

In more complex scenarios there might be the need to do a few special steps at the start of a test (and also after a test), like open and close a database connection.
For this reason the unit test framework provides us the two methods setUp() and tearDown(). If we provide these two functions in our testcase, they are run before and after each test.

unittest with setUp and tearDown methods
import unittest
import simple_tool
 
class TestInternalCalculation(unittest.TestCase):
    def setUp(self):
        print "SETUP"
    def tearDown(self):
        print "TEARDOWN"
    def test_simple_number(self):
        print "TEST ONE"
        self.assertEqual(simple_tool._internal_calculation(3), 1./3)
    def test_simple_number_two(self):
        print "TEST TWO"
        self.assertEqual(simple_tool._internal_calculation(4), 1./4)
 
if _name_ == '_main_':
    unittest.main()

To illustrate the execution sequence I have augmented the functions with print statements

python test_simple_tool.py
SETUP
TEST ONE
TEARDOWN
.SETUP
TEST TWO
TEARDOWN
.
----------------------------------------------------------------------
Ran 2 tests in 0.000sOK

What to assert

The aforementioned assertEqual(a, b) is only one of many test methods we can use to check the correctness of our modules.
The unit test module defines methods with descriptive names like

  • assertTrue(x)
  • assertIsNotNone(x)
  • assertGreater(a,b)
  • assertDictContainsSubset(a,b)

For a complete list of all provided assert methods please check the official documentation at https://docs.python.org/2/library/unittest.html#test-cases

There are also possibilities to check whether a method under test throws an exception. One of them is assertRaises(exc, fun, *args, **kwds).
There are two ways to use this assertion. We can simply call the assertion or in a special way using a contextmanager.

testing for a thrown exception
def test_input_zero_normal_way(self):
    self.assertRaises(Exception, simple_tool._internal_calculation, 0)
 
def test_input_zero_with_contextmanager(self):
    with self.assertRaises(Exception):
        _internal_calculation(0)

Conclusion and further reading

The Python unittest module [1] provides a complete framework to write tests along with your code. By using it you can for example implement TDD (test driven development) to stabilize your codebase. This article has only scratched the surface on what you can do. For a complete overview please refer to [1].
The additional module pyhamcrest [2] extends the unittest module and enables you to write literal test like assert_that(testValue, is_(instance_of(groundtruth))).
For a more complete overview of Python test frameworks please refer to [3].

Links

[1] The Python unittest module
[2] PyHamcrest
[3] Python unit testing overview