Unit testing Python programs: PyUnit / unittest module HOWTO

Copyright 2001 AdamFeuer (Resdistributable under the Gnu Free Documentation License. For license information, see below.)

What is this document?

This is meant to provide a quick introduction and "how-to" guide for PyUnit, Python's unittest unit testing module. This is not meant as a definitive guide; for more information see the Python Reference manual, or the other resources listed below.

Why do unit testing?

(From the online book DiveIntoPython.)

What Is PyUnit?

PyUnit is a framework that makes it easy to write automated test suites in Python. It's based on JUnit, the definitive Java unit testing framework, which is based on Kent Beck's Smalltalk unit testing framework.

PyUnit makes it easy to write test cases that test a unit of functionality like a single method, and also makes it easy to group these testcases into test suites that would test whole classes or functional blocks. Because the tests are automated, it's easy to run them whenever you change your program...

PyUnit comes included as the unittest module in Python 2.1 and above.

Overview

PyUnit provides three main classes for us to use. You will spend most of your time using unittest.TestCase. unittest.TestSuite is used occasionally to group TestCase objects together. unittest.TextTestRunner is used to run the test suite, and once you have your framework running, you probably won't have to look at it again.

unittest.TestCase

This is the work horse that we will be using quite a bit. Here's some code:

  class CaseCheck(unittest.TestCase):                  
    def testToRomanCase(self):                       
        """toRoman should always return uppercase""" 
        for integer in range(1, 4000):               
            numeral = roman.toRoman(integer)         
            self.assertEqual(numeral, numeral.upper())     

    def testFromRomanCase(self):                     
        """fromRoman should only accept uppercase input"""
        for integer in range(1, 4000):               
            numeral = roman.toRoman(integer)         
            roman.fromRoman(numeral.upper())         
            self.assertRaises(roman.InvalidRomanNumeralError,
                              roman.fromRoman, numeral.lower()) 

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

(From the online book DiveIntoPython.)

Here are a couple of things to note:

Things not shown here:

unittest.TestSuite

Note that you probably don't have to bother with this if you use the TestRunner.py program below!

  def suite():
     suite = unittest.TestSuite()
     suite.addTest(WidgetTestCase("testDefaultSize"))
     suite.addTest(WidgetTestCase("testResize"))
     return suite

TestSuite objects are containers for TestCases and other TestSuites. Since TestSuites can hold other TestSuites, you can have a hierarchy of tests.

There are two main methods, both just add tests to the TestSuite container:

unittest.TextTestRunner

Again, you probably won't have to bother with this if you use the TestRunner.py program, below!

    suite = unittest.TestSuite()
    suite.addTest(DataStoreTest.Test())

    ttr = unittest.TextTestRunner()
    results = ttr.run(suite)

TextTestRunner is a way of presenting the results nicely in a text terminal window. You just pass in an object derived from TestSuite to its constructor, then call run, and it does the rest.

TestRunner.py - An automated unit test system

Mark Pilgrim gives a short but very useful program to run all your tests automatically in Dive Into Python Chapter 7. If your testfiles all end in "test.py", this program will import all the classes in these files, make a TestSuite out of the TestCases or TestSuites they contain, and start unittest with that TestSuite. I call this program 'TestRunner.py':

   """Regression testing framework
   This module will search for scripts in the same directory named
   XYZTest.py.  Each such script should be a test suite that tests a
   module through PyUnit.  (As of Python 2.1, PyUnit is included in
   the standard library as "unittest".)  This script will aggregate all
   found test suites into one big test suite and run them all at once.
   """

   import unittest

   import sys, os, re, unittest

   def regressionTest():
   path = os.path.split(sys.argv[0])[0] or os.getcwd()
   files = os.listdir(path)
   test = re.compile("test.py$", re.IGNORECASE)
   files = filter(test.search, files)
   filenameToModuleName = lambda f: os.path.splitext(f)[0]
   moduleNames = map(filenameToModuleName, files)
   modules = map(__import__, moduleNames)
   load = unittest.defaultTestLoader.loadTestsFromModule
   return unittest.TestSuite(map(load, modules))

   if __name__ == "__main__":
      unittest.main(defaultTest="regressionTest")

unittestgui.py - GUI interface to PyUnit

This is a small Tkinter GUI written in Python, similar to JUnit's GUI. If you like GUIs, this gives a way to hit a button and run all your tests. It shows the progress of the tests with a moving bar, which stays green as long as the tests pass, and goes red as soon as one test fails. It gives you a list of the tests that failed, and you can double-click the test to get the traceback detail.

Usage notes: you only get this if you download the PyUnit source, it doesn't come with Python. I could only get it to work correctly if I copied the unittestgui.py file to my test directory and ran it from there; for some reason, it doesn't have the Python import path set right unless you do this.

If you use the program TestRunner.py above, and you have the it, your tests, and the unittestgui.py file in your current directory, you can start the GUI like this:

   $ python unittestgui.py TestRunner.regressionTest &

(You have to give unittestgui.py the name of an object that will return a TestSuite instance.)

Resources

Copyright and license info for this document

Permission is granted to copy, distribute, and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.

For more information see http://www.gnu.org/copyleft/fdl.html

PyUnitHowto (last edited 2008-03-04 08:33:24 by localhost)