Note: This recipe is superceded by TestOOB, a Python unit testing framework that extends unittest and provides many new features - including running tests in threads! http://testoob.sourceforge.net
Trying to extend unittest to provide extra features wasn't easy. This scheme allows easy extensions for running existing test suites.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | """
An alternative running scheme for unittest test suites.
Superceded by the TestOOB Python unit testing framework,
http://testoob.sourceforge.net
"""
__author__ = "Ori Peleg"
import unittest, sys
from itertools import ifilter
###############################################################################
# apply_runner
###############################################################################
# David Eppstein's breadth_first
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/231503
def _breadth_first(tree,children=iter):
"""Traverse the nodes of a tree in breadth-first order.
The first argument should be the tree root; children
should be a function taking as argument a tree node and
returning an iterator of the node's children.
"""
yield tree
last = tree
for node in _breadth_first(tree,children):
for child in children(node):
yield child
last = child
if last == node:
return
def extract_fixtures(suite, recursive_iterator=_breadth_first):
"""Extract the text fixtures from a suite.
Descends recursively into sub-suites."""
def test_children(node):
if isinstance(node, unittest.TestSuite): return iter(node)
return []
return ifilter(lambda test: isinstance(test, unittest.TestCase),
recursive_iterator(suite, children=test_children))
def apply_runner(suite, runner_class, result_class=unittest.TestResult,
test_extractor=extract_fixtures):
"""Runs the suite."""
runner = runner_class(result_class)
for fixture in test_extractor(suite):
runner.run(fixture)
return runner.result()
###############################################################################
# Runners
###############################################################################
class SimpleRunner:
def __init__(self, result_class):
self._result = result_class()
self._done = False
def run(self, fixture):
assert not self._done
fixture(self._result)
def result(self):
self._done = True
return self._result
# Connelly Barnes's (connellybarnes at yahoo.com) threadclass
# http://mail.python.org/pipermail/python-list/2004-June/225478.html
import types, threading
def _threadclass(C):
"""Returns a 'threadsafe' copy of class C.
All public methods are modified to lock the
object when called."""
class D(C):
def __init__(self, *args, **kwargs):
self.lock = threading.RLock()
C.__init__(self, *args, **kwargs)
def ubthreadfunction(f):
def g(self, *args, **kwargs):
self.lock.acquire()
try:
return f(self, *args, **kwargs)
finally:
self.lock.release()
return g
for a in dir(D):
f = getattr(D, a)
if isinstance(f, types.UnboundMethodType) and a[:2] != '__':
setattr(D, a, ubthreadfunction(f))
return D
class ThreadedRunner(SimpleRunner):
"""Run tests using a threadpool.
Uses TwistedPython's thread pool"""
def __init__(self, result_class):
from twisted.python.threadpool import ThreadPool
SimpleRunner.__init__(self, _threadclass(result_class))
self._pool = ThreadPool()
self._pool.start()
def run(self, fixture):
assert not self._done
self._pool.dispatch(None, fixture, self._result)
def result(self):
self._pool.stop()
return SimpleRunner.result(self)
###############################################################################
# text_run
###############################################################################
def _print_results(result, timeTaken):
# code modified from Python 2.4's standard unittest module
stream = result.stream
result.printErrors()
stream.writeln(result.separator2)
run = result.testsRun
stream.writeln("Ran %d test%s in %.3fs" %
(run, run != 1 and "s" or "", timeTaken))
stream.writeln()
if not result.wasSuccessful():
stream.write("FAILED (")
failed, errored = map(len, (result.failures, result.errors))
if failed:
stream.write("failures=%d" % failed)
if errored:
if failed: stream.write(", ")
stream.write("errors=%d" % errored)
stream.writeln(")")
else:
stream.writeln("OK")
class _TextTestResult(unittest._TextTestResult):
"""provide defaults for unittest._TextTestResult"""
def __init__(self, stream = sys.stderr, descriptions=1, verbosity=1):
stream = unittest._WritelnDecorator(stream)
unittest._TextTestResult.__init__(self, stream, descriptions, verbosity)
def text_run(suite, runner_class=SimpleRunner, **kwargs):
"""Run a suite and generate output similar to unittest.TextTestRunner's"""
import time
start = time.time()
result = apply_runner(suite, runner_class, result_class=_TextTestResult,
**kwargs)
timeTaken = time.time() - start
_print_results(result, timeTaken)
###############################################################################
# Test extractors
###############################################################################
def regexp_extractor(regexp):
"""Filter tests based on matching a regexp to their id.
Matching is performed with re.search"""
import re
compiled = re.compile(regexp)
def pred(test): return compiled.search(test.id())
def wrapper(suite):
return ifilter(pred, extract_fixtures(suite))
return wrapper
###############################################################################
# examples
###############################################################################
def examples(suite):
print "== sequential =="
text_run(suite)
print "== threaded =="
text_run(suite, ThreadedRunner)
print "== filtered =="
text_run(suite, test_extractor = regexp_extractor("Th"))
|
See the 'examples' function for simple usage similar to unittest's text runner. For running tests with different output schemes, see the 'text_run' function's code.
Implementation notes: * Note the ease of plugging in a new runner (see ThreadRunner) and of using test extractors (see regexp_extractor for filtering tests based on a name regexp).
class ThreadedRunner uses TwistedPython's thread pool. It can easily be replaced with any other thread pool.
The test fixtures can run in parallel even in the presence of setUp/tearDown, because each fixture has an independent instance of the relevant TestCase. Thanks to Steve Purcell for pointing this out (and for unittest itself :-)
I'd love to hear any comments and ideas.
High-latency test suites. Using the threaded runner is great for suites of high-latency tests that aren't CPU-bound.
An example suite is below.
Output for running examples(suite()):
---- begin output ----
---- end output ----
The suite:
Hi Ori Peleg, We are using the above recipe but we have not used extract_fixtures, instead we are using entire suite like below
for fixture in test_extractor(suite):
for fixture in suite:
The method which was given by you executes only setupmethod,testmethod and teardownmethods, but it will not execute setupclass and teardownclass. Hence we have used suite instead of test_extractor and unittest2 instead of unittest for this. But the issue that we are facing currently is setupclass and teardownclass are getting executed multiple times if we are using threaded runner(i.e. when we are trying to execute unittest classes parallely).
Please suggest some solution for this as soon as possible.