4 # Author: Garth Kidd <garth@deadlybloodyserious.com>
5 # Copyright: This module has been placed in the public domain.
8 This module extends unittest.py with `loadTestModules()`, by loading multiple
9 test modules from a directory. Optionally, test packages are also loaded,
21 # So that individual test modules can share a bit of state,
22 # `package_unittest` acts as an intermediary for the following
28 Usage: test_whatever [options]
31 -h, --help Show this message
32 -v, --verbose Verbose output
33 -q, --quiet Minimal output
34 -d, --debug Debug mode
37 def usageExit(msg
=None):
38 """Print usage and exit."""
44 def parseArgs(argv
=sys
.argv
):
45 """Parse command line arguments and set TestFramework state.
47 State is to be acquired by test_* modules by a grotty hack:
48 ``from TestFramework import *``. For this stylistic
49 transgression, I expect to be first up against the wall
50 when the revolution comes. --Garth"""
51 global verbosity
, debug
53 options
, args
= getopt
.getopt(argv
[1:], 'hHvqd',
54 ['help', 'verbose', 'quiet', 'debug'])
55 for opt
, value
in options
:
56 if opt
in ('-h', '-H', '--help'):
58 if opt
in ('-q', '--quiet'):
60 if opt
in ('-v', '--verbose'):
62 if opt
in ('-d', '--debug'):
65 usageExit("No command-line arguments supported yet.")
66 except getopt
.error
, msg
:
69 def loadTestModules(path
, name
='', packages
=None):
71 Return a test suite composed of all the tests from modules in a directory.
73 Search for modules in directory `path`, beginning with `name`. If
74 `packages` is true, search subdirectories (also beginning with `name`)
75 recursively. Subdirectories must be Python packages; they must contain an
78 testLoader
= unittest
.defaultTestLoader
79 testSuite
= unittest
.TestSuite()
81 path
= os
.path
.abspath(path
) # current working dir if `path` empty
86 for filename
in files
:
87 if filename
.startswith(name
):
88 fullpath
= os
.path
.join(p
, filename
)
89 if filename
.endswith('.py'):
90 fullpath
= fullpath
[len(path
)+1:]
91 testModules
.append(path2mod(fullpath
))
92 elif packages
and os
.path
.isdir(fullpath
) and \
93 os
.path
.isfile(os
.path
.join(fullpath
, '__init__.py')):
94 paths
.append(fullpath
)
95 # Import modules and add their tests to the suite.
96 sys
.path
.insert(0, path
)
97 for mod
in testModules
:
99 print >>sys
.stderr
, "importing %s" % mod
101 module
= import_module(mod
)
103 print >>sys
.stderr
, "ERROR: Can't import %s, skipping its tests:" % mod
104 sys
.excepthook(*sys
.exc_info())
106 # if there's a suite defined, incorporate its contents
108 suite
= getattr(module
, 'suite')
109 except AttributeError:
110 # Look for individual tests
111 moduleTests
= testLoader
.loadTestsFromModule(module
)
112 # unittest.TestSuite.addTests() doesn't work as advertised,
113 # as it can't load tests from another TestSuite, so we have
115 testSuite
.addTest(moduleTests
)
117 if type(suite
) == types
.FunctionType
:
118 testSuite
.addTest(suite())
119 elif isinstance(suite
, unittest
.TestSuite
):
120 testSuite
.addTest(suite
)
122 raise AssertionError, "don't understand suite (%s)" % mod
127 """Convert a file path to a dotted module name."""
128 return path
[:-3].replace(os
.sep
, '.')
130 def import_module(name
):
131 """Import a dotted-path module name, and return the final component."""
132 mod
= __import__(name
)
133 components
= name
.split('.')
134 for comp
in components
[1:]:
135 mod
= getattr(mod
, comp
)
138 def main(suite
=None):
140 Shared `main` for any individual test_* file.
142 suite -- TestSuite to run. If not specified, look for any globally defined
147 # Load any globally defined tests.
148 suite
= unittest
.defaultTestLoader
.loadTestsFromModule(
149 __import__('__main__'))
151 print >>sys
.stderr
, "Debug: Suite=%s" % suite
152 testRunner
= unittest
.TextTestRunner(verbosity
=verbosity
)
153 # run suites (if we were called from test_all) or suite...
154 if type(suite
) == type([]):
158 return testRunner
.run(suite
)