Peter's Blog

Redefining the Impossible

Python Unit Tesing with less pain


Further to my python unit testing musings I have determined the following problems/workarounds with the unittest module. Here is a summary of something that works:

   1  import unittest
   2  import re
   3  
   4  class Scenario:
   5      def __init__( self):
   6          print 'Set thing up once and once only'
   7  
   8      def __del__( self):
   9          print 'Finally clean up'
  10  
  11  class OrderedTestLoader( unittest.TestLoader):
  12      def __init__( self, strFileName):
  13          self.strOrderedNames = re.findall( r'def (test[a-zA-Z0-9_]+)\b',
  14                                              open( strFileName).read())
  15          self.strOrderedNames.reverse()
  16  
  17      def getTestCaseNames( self, testCaseClass):
  18          """
  19          Return test method names in order of definitions.
  20          """
  21          #
  22          # Call base function and return whatever it returns
  23          # but with entries sorted.
  24          #
  25          strNames = unittest.TestLoader.getTestCaseNames( self, testCaseClass)
  26          strOrderedNames = self.strOrderedNames
  27  
  28          for strName in strOrderedNames:
  29              if strName in strNames:
  30                  strNames.remove( strName)
  31                  strNames.insert( 0, strName)
  32  
  33          return strNames
  34  
  35  class BetterTest( unittest.TestCase):
  36      oScenario = Scenario()
  37  
  38      def setUp( self):
  39          print "Called before each test"
  40  
  41      def testTestOne( self):
  42          print 'blah'
  43  
  44      def testTestTwo( self):
  45          print 'blah blah'
  46  
  47      def testTestThree( self):
  48          print 'blah blah blah'
  49  
  50      def tearDown( self):
  51          print 'Called after each test'
  52  
  53  unittest.main( testLoader = OrderedTestLoader( __file__))

Here the Scenario class defines a test scenario that is in place throughout the test. In my case I am testing against data that should be in a database throughout the test. I do not want to keep creating and deleting that data for each test so I create it at the start of the test and delete it at the end.

For some reason, the unittest module creates as many instances of your TestCase class as there are test methods in that class so you cannot simply define an __init__ function in your TestCase class.

The __del__ method of the Scenario is used to clean up because the unittest.main() function calls sys.exit() when the test is complete so no code defined after that line is executed.

OrderedTestLoader modifies the order of execution such that the methods are executed in the order of definition. This may make things more intuitive and allows the byproducts of one test to become the input to the next. This may not be a totally pure methodology but I don't care. Note that this is not perfect, if you have two test classes containing methods with the same name then it might get confused. It may be better to make duplicate function names illegal.

One other advantage of unittest over doctest: you can set breakpoints in the code and single step through it more easily.


Filed under: python

Peter Says:

over 2 years ago

More points:

  • I have put OrderedTestLoader in a library module.
  • Scenario.__del__ is called while python is being terminated and it may fail because modules it is using have been unloaded. It would be better to have a final ordered test step that did the tidying up.
  • the big drawback of the sequential nature of these tests is that you have to run all the tests, you cannot run just the one which is giving you problems. However, you save writing a lot of setup code. It really depends how long it takes your tests to run. There is nothing in this scheme to prohibit ever setting up for each individual test.

Peter

Faheem Mitha Says:

about 1 year ago

I'm interested in this technique. However, I must be missing something. How do I get the __del__ method to be called? See the code below, with output.

Sorry the format is so lousy. I couldn't figure out how to format it sensibly. Please email me with any response.

Thanks, Faheem.

*********************************************************************

import unittest

class Fixture:

def __init__(self): print "add fixture stuff"

def __del__(self): print "delete fixture stuff"

class doTest(unittest.TestCase): ofoo = Fixture()

def setUp(self): print "called before each test"

def testTestOne(self): print "calling test 1"

def testTestTwo(self): print "calling test 2"

def tearDown(self): print "called after each test"

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

********************************************************************* .. ----------------------------------------------------------------------

Ran 2 tests in 0.000s

OK

add fixture stuff

called before each test

calling test 1

called after each test

called before each test

calling test 2

called after each test

Peter Says:

about 1 year ago

__del__ is called either automatically from the garbage collector when the object is destroyed or it can be called more explicitly by using the del statement.

e.g.

   1  
   2  class X:
   3    def __del__( self):
   4       print "Goodbye cruel world"
   5  
   6  o = X()
   7  
   8  del o
   9  
  10  print "It is now gone"
  11  

Peter

Comments are Closed