Kent's Korner
by Kent S Johnson

2008-10-19 15:08:20

unittest

A brief introduction to unit testing with the Python unittest module

Unit tests are functional tests for component parts of an application. Unit tests can be written for individual functions or entire classes. Unit tests provide a simple, automated way to verify correctness of the unit under test as it is written and as changes are made.

There are many ways to write unit tests for Python programs. The unittest and doctest modules are part of the standard library. nose and py.test are popular third-party test frameworks.

unittest is a classic "xUnit-style" unit test framework based on Kent Beck's original SmalltalkUnit. unittest is a bit verbose and slightly more complex than the other options; this is one reason nose and py.test were written. unittest also suffers from documentation that highlights rarely used parts of the library. The advantage of unittest is that it is in the standard library, and that similar test frameworks are available for virtually any language. unittest also allows a test runner to be part of the module under test; I'm not sure if nose and py.test can do that.

Grig Gheorghiu wrote several blog posts comparing unittest, doctest and py.test.

Anatomy of a test

Here is a minimal example of a simple function with a simple test:

def even(x):                            #1
    ''' Returns True if x is even, False otherwise. '''
    return True

if __name__ == '__main__':              #2
    import unittest                     #3

    class TestEven(unittest.TestCase):  #4
        def test1(self):                #5
            self.assert_(even(2))       #6

    unittest.main()                     #7
  • #1 is the function to test.
  • #2 begins the "main" section of the module. This code will not be run when the module is imported by client code. Putting the unit test in the "main" section of a module allows the test to be in the same file as the code under test. You can also put the tests in a separate module.
  • #3 imports the unittest module.
  • #4 defines the test class. Every test must be included in a class that inherits from unittest.TestCase. The class name should start with Test. unittest uses naming conventions and introspection to find tests.
  • #5 begins a test method. Test method names should start with test. The method takes no parameters other than self.
  • #6 contains the meat. This line calls the even() function and asserts that its result is True. Note that the assertion function is called assert_() with an underscore. This is because assert is a Python keyword.
  • #7 calls unittest.main(). This function looks for test classes and test cases in the current module and runs the tests it finds.

The output of this program is simple and informative

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

For each test that succeeds, unittest.main() prints a single period, allowing you to watch the progress of the tests. When all tests have run, it prints a summary.

This test is obviously inadequate. Let's add another test:

def test2(self):
  self.failIf(even(3))

The TestCase class includes many assertXxx() and failXxx() methods for checking results. assert_() and failIf() are two of the simplest, they just check for True or False.

Now the output is more interesting:

.F
======================================================================
FAIL: test2 (__main__.TestEven)
----------------------------------------------------------------------
Traceback (most recent call last):
  method test2 in unittestex1.py at line 12
    self.failIf(even(3))
AssertionError

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

FAILED (failures=1)

The output still shows a . for the successful test. It also shows an F for the failed test, the name of the failed test, and a full traceback showing which test failed. The final summary has changed from OK to FAILED (failures=1).

assertEqual()

Many tests compare expected and actual values. While these tests can be written with assert_() and a boolean expression, it's often better to use one of the two-argument assertions such as assertEqual(). The two-argument assertions give more informative error messages, showing the expected and actual values.

For example, compare the output of these two tests:

def test3(self):
  x = 1
  self.assert_(x == 2)

def test4(self):
  x = 1
  self.assertEqual(x, 2)
======================================================================
FAIL: test3 (__main__.TestEven)
----------------------------------------------------------------------
Traceback (most recent call last):
  method test3 in unittestex1.py at line 15
    self.assert_(x == 2)
AssertionError

======================================================================
FAIL: test4 (__main__.TestEven)
----------------------------------------------------------------------
Traceback (most recent call last):
  method test4 in unittestex1.py at line 18
    self.assertEqual(x, 2)
AssertionError: 1 != 2

The first output gives no clue to the actual value of x. The second one shows both values.

setUp() and tearDown()

The setUp() method runs before each test method. tearDown() runs after each test. This is the place for boilerplate that is shared between tests. By assigning to attributes of self you can provide data for each test.

assertRaises()

Use assertRaises() to test for expected exceptions. Here is a simple example:

def divide(x, y):
    return x/y

if __name__ == '__main__':
    import unittest

    class TestDivide(unittest.TestCase):
        def test1(self):
            self.assertEquals(3, divide(6, 2)) #1

        def test2(self):
            self.assertRaises(ZeroDivisionError, divide, 6, 0) #2

    unittest.main()

Notice that the arguments to assertRaises() are the expected exception, the function to call, and the arguments to the function under test. Compare #1, which actually calls divide(), to #2, which passes the divide function and its arguments to assertRaises().

Test discovery

A common pattern is to write a test module for each module to be tested. It's a good idea to occasionally run all the tests for a project, perhaps before a checkin or as part of a nightly build. For this, you need a way to discover all tests. The unittest module uses TestSuite objects for this. It is a bit awkward to use. I prefer to use the nosetests facility of nose as a test runner when I want to run multiple tests.

Further reading

 
© Kent S Johnson Creative Commons License

Short, introductory essays about Python.

kentsjohnson.com

Kent's Korner home

Weblog

Other Essays