Wednesday, July 26, 2006

Python unittest whole directory tree with test cases

I have a "test" folder with testcase classes, and nested folders with other testcase classes.
I wrote a small script (got some ideas from reportlab) that recurses the whole test folder and collects all classes which extend unittest.TestCase. They are automatically added to a large testsuite. The advantage is that the TestCase class does not need to create a suite itself, and can be as sinle as this. All methods which start with "test" are considered as testing methods.
 import unittest

class TestHello(unittest.TestCase):
def testHello(self):
assert 1 == 1

A pattern can be used to tell the script which files to take into account as testCase candidates.
 
import os
import sys
import fnmatch
import unittest
import traceback
import types


def getTestCaseClasses(module):
"""
Returns all TestCase classes in a module.
"""
testCases = []

def isClass(object):
"""Return true if the object is a class.

Class objects provide these attributes:
__doc__ documentation string
__module__ name of module in which this class was defined
"""
return type(object) is types.ClassType or hasattr(object, '__bases__')

for name in dir(module):
obj = getattr(module, name)
if isClass(obj):
if unittest.TestCase in obj.__bases__:
testCases.append(obj)

return testCases


def getTestSuite(folder, exclude=None, nonImportable=None, pattern='test_*.py'):
"""
Build a test suite of all available test files.
Recurses into the subfilders
"""
if exclude is None:
exclude = []
if nonImportable is None:
nonImportable = []

allTests = unittest.TestSuite()

if os.path.isdir(folder):
sys.path.insert(0, folder)

for root, dirs, filenames in os.walk('.'):
filenames = fnmatch.filter(filenames, pattern)
for filename in filenames:
modname = os.path.splitext(os.path.basename(filename))[0]
if modname not in exclude:
try:
packagePath = root.split(os.sep) + [modname]
modname = '.'.join([p for p in packagePath if p !='.'])
exec 'import %s as module' % modname
testCaseClasses = getTestCaseClasses(module)
for tcc in testCaseClasses:
suite = unittest.makeSuite(tcc, 'test')
allTests.addTest(suite)
except:
tt, tv, tb = sys.exc_info()[:]
nonImportable.append((filename,traceback.format_exception(tt,tv,tb)))
del tt,tv,tb
del sys.path[0]

return allTests


def main(pattern='test_*.py'):
try:
folder = os.path.dirname(__file__)
except:
folder = os.path.dirname(sys.argv[0]) or os.getcwd()

nonImportable = []
testSuite = getTestSuite(folder, nonImportable=nonImportable, pattern=pattern)
unittest.TextTestRunner(verbosity=2).run(testSuite)

if nonImportable:
print '\n#########################################'
print 'the following tests could not be imported'
print '##########################################\n'
for f,tb in nonImportable:
print 'file: "%s"\n%s\n' % (f,''.join([' '+l for l in tb]))

if __name__ == '__main__':
main()

No comments: