4 # Contact: garth@deadlybloodyserious.com
7 # Copyright: This module has been placed in the public domain.
10 This module extends unittest.py with `loadTestModules()`, by loading multiple
11 test modules from a directory. Optionally, test packages are also loaded,
23 # So that individual test modules can share a bit of state,
24 # `package_unittest` acts as an intermediary for the following
30 Usage: test_whatever [options]
33 -h, --help Show this message
34 -v, --verbose Verbose output
35 -q, --quiet Minimal output
36 -d, --debug Debug mode
39 def usageExit(msg
=None):
40 """Print usage and exit."""
46 def parseArgs(argv
=sys
.argv
):
47 """Parse command line arguments and set TestFramework state.
49 State is to be acquired by test_* modules by a grotty hack:
50 ``from TestFramework import *``. For this stylistic
51 transgression, I expect to be first up against the wall
52 when the revolution comes. --Garth"""
53 global verbosity
, debug
55 options
, args
= getopt
.getopt(argv
[1:], 'hHvqd',
56 ['help', 'verbose', 'quiet', 'debug'])
57 for opt
, value
in options
:
58 if opt
in ('-h', '-H', '--help'):
60 if opt
in ('-q', '--quiet'):
62 if opt
in ('-v', '--verbose'):
64 if opt
in ('-d', '--debug'):
67 usageExit("No command-line arguments supported yet.")
68 except getopt
.error
, msg
:
71 def loadTestModules(path
, name
='', packages
=None):
73 Return a test suite composed of all the tests from modules in a directory.
75 Search for modules in directory `path`, beginning with `name`. If
76 `packages` is true, search subdirectories (also beginning with `name`)
77 recursively. Subdirectories must be Python packages; they must contain an
80 testLoader
= unittest
.defaultTestLoader
81 testSuite
= unittest
.TestSuite()
83 path
= os
.path
.abspath(path
) # current working dir if `path` empty
88 for filename
in files
:
89 if filename
.startswith(name
):
90 fullpath
= os
.path
.join(p
, filename
)
91 if filename
.endswith('.py'):
92 fullpath
= fullpath
[len(path
)+1:]
93 testModules
.append(path2mod(fullpath
))
94 elif packages
and os
.path
.isdir(fullpath
) and \
95 os
.path
.isfile(os
.path
.join(fullpath
, '__init__.py')):
96 paths
.append(fullpath
)
97 # Import modules and add their tests to the suite.
98 sys
.path
.insert(0, path
)
99 for mod
in testModules
:
101 print >>sys
.stderr
, "importing %s" % mod
102 module
= import_module(mod
)
103 # if there's a suite defined, incorporate its contents
105 suite
= getattr(module
, 'suite')
106 except AttributeError:
107 # Look for individual tests
108 moduleTests
= testLoader
.loadTestsFromModule(module
)
109 # unittest.TestSuite.addTests() doesn't work as advertised,
110 # as it can't load tests from another TestSuite, so we have
112 testSuite
.addTest(moduleTests
)
114 if type(suite
) == types
.FunctionType
:
115 testSuite
.addTest(suite())
116 elif type(suite
) == types
.InstanceType \
117 and isinstance(suite
, unittest
.TestSuite
):
118 testSuite
.addTest(suite
)
120 raise AssertionError, "don't understand suite (%s)" % mod
125 """Convert a file path to a dotted module name."""
126 return path
[:-3].replace(os
.sep
, '.')
128 def import_module(name
):
129 """Import a dotted-path module name, and return the final component."""
130 mod
= __import__(name
)
131 components
= name
.split('.')
132 for comp
in components
[1:]:
133 mod
= getattr(mod
, comp
)
136 def main(suite
=None):
138 Shared `main` for any individual test_* file.
140 suite -- TestSuite to run. If not specified, look for any globally defined
145 # Load any globally defined tests.
146 suite
= unittest
.defaultTestLoader
.loadTestsFromModule(
147 __import__('__main__'))
149 print >>sys
.stderr
, "Debug: Suite=%s" % suite
150 testRunner
= unittest
.TextTestRunner(verbosity
=verbosity
)
151 # run suites (if we were called from test_all) or suite...
152 if type(suite
) == type([]):
156 testRunner
.run(suite
)