Unit Testing in Python

This page is a short tutorial on unit testing in Python, using the PyUnit module that ships with Python. I assume the reader is familiar with xUnit test frameworks in general, for example JUnit for Java and NUnit for .NET. I also assume the reader is a new Python programmer (which I am), so I will explain Python concepts more than I will explain xUnit concepts. And finally, I assume that your primary programming languages are C# and Ruby. I have based these reader assumptions on myself because I also assume I will be the primary reader of this page.

There is an introduction to the Python Unit Testing Framework that I tried to read, but I found it hard to follow because it is a depth-first exposition. You have to read all about setup and teardown methods, testcase classes with several test methods, aggregating tests into test suites, nesting test suites, and a discussion of how to organize large bodies of test code before you can run the simplest test. I got frustrated because I just wanted to know the simplest thing I could do to test my code. That is why I am writing my own tutorial.

A Unit to Test

We need to have an example to test. Wanting to keep this demo as simple as possible, I have decided to specify some really simple requirements.

  1. You must write a class named ClassUnderTest.
  2. This class must provide a method named krajik.
  3. The krajik method must accept a number and return a number twice the given number.

Importing the Unit Test Module

Before you can use the types in the unit test module, you have to import it. Here is a simple way to import it:

import unittest

The Testcase Class

To write a testcase class, write a class that derives from the TestCase class of the unittest module:

class CheckCUT(unittest.TestCase):
    def runTest(self):
        # test procedure goes here...

On the class statement, the base class (or classes) goes in parentheses after the name of the class. I say classes here because Python supports multiple inheritance. I have never tried using it, though.

The class overrides the runTest method.

Asserting

Write the usual four-phase unit test: Setup, Exercise, Verify, and Teardown. In this test, the teardown is done automatically by the garbage collector:

class CheckCUT(unittest.TestCase):
    def runTest(self):
        cut = ClassUnderTest()
        actual = cut.krajik(17)
        expected = 34
        assert expected == actual, 'you are screwed'

I just copied the assert statement from the site mentioned earlier and edited it for this scenario. I do not understand enough Python to really understand its syntax. The referenced page says this:

Note that in order to test something, we just use the built-in 'assert' statement of Python. If the assertion fails when the test case runs, an AssertionError will be raised, and the testing framework will identify the test case as a 'failure'.

Running the Test

To run the test, you have to construct an object of the CheckCUT class, construct a TextTestRunner, and finally ask the runner to run your test case. Like this:

testCase = CheckCUT()

runner = unittest.TextTestRunner()
runner.run(testCase)

Then you just invoke the script from the command line. Here is what you get when you run what we have so far:

E:\PyUnit>ut
E
======================================================================
ERROR: runTest (__main__.CheckCUT)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "E:\PyUnit\ut.py", line 16, in runTest
    cut = ClassUnderTest();
NameError: global name 'ClassUnderTest' is not defined

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Of course it failed because we have not yet written the implementation.

Write the Code

Now let us see if we can fix the not defined error. First, let us write a class that contains the method to be sure it is declared correctly. And here it is:

class ClassUnderTest :
    def krajik(self, foo):
        return 0

In some ways Python is a very clean language in terms of not having a lot of needless punctuation. For example, notice the refreshing lack of braces and semicolons. The block structure of the code is indicated strictly by its level of indentation. However, like all languages, it has its idiosyncracies. In the case of Python, notice the gratuitous colons at the ends of the class and def lines. Those colons also show up at the ends of if and while statements. In fact, the basic use of a colon is to say redundantly the next line should be indented. So anyway, the Python evangelists who wax eloquent about their favorite lovely language, tend to overlook this little detail.

Enough whingeing for the moment. The class statement contains a method definition, which is indicated by the def keyword and another colon. Notice that you must always remember to put in a self keyword as the first argument. I guess that is a replacement for leaving out a static keyword for instance methods.

Like Ruby, you do not need to declare variables before you assign to them. In fact, the first assignment that is executed for a given variable is a combination of setting a value and declaring the variable. In the case of a method parameter, it just acts like it is an assignment from the value of the argument that was used to call the method.

Run the Test Again

Now when we run the test we get this result:

E:\PyUnit>ut
F
======================================================================
FAIL: runTest (__main__.CheckCUT)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "E:\PyUnit\ut.py", line 19, in runTest
    assert expected == actual, 'you are screwed'
AssertionError: you are screwed

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

FAILED (failures=1)

We have fixed the problem with the class being undefined, but we still have a virtual red bar, because we have not implemented the method correctly.

In other xUnit test frameworks, the message would have been more like this:

you are screwed: Expected 34 but got 0

I think I prefer the version that tells you what the expected and actual values are. I guess PyUnit is not really ready for prime time yet.

Fix the Implementation

Now let us fix the error and make the test pass:

class ClassUnderTest :
    def krajik(self, foo):
        return foo * 2

Green Bar

Now when we run the test, it passes. This is what we see:

E:\PyUnit>ut
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Summary

This tutorial has shown you the very simplest thing you can do to use the PyUnit framework without bogging you down with a lot of details that would only distract you at the beginning. Naturally, you will want to learn about those details after you get this much working. To learn those details, you could go to this site:

Python Unit Testing Framework.

Last updated August 14, 2010