2 XML Test Runner for PyUnit
16 from xml.sax.saxutils
import escape
19 from io
import StringIO
21 from io
import StringIO
26 """Information about a particular test.
28 Used by _XMLTestResult.
33 (self._class, self._method) = test.id().rsplit(
".", 1)
34 self.
_time_time = time
40 """Create a _TestInfo instance for a successful test."""
45 """Create a _TestInfo instance for a failed test."""
47 info._failure = failure
52 """Create a _TestInfo instance for an erroneous test."""
58 """Print information about this test case in XML format to the
62 stream.write(
' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \
65 "method": self._method,
66 "time": self.
_time_time,
68 if self.
_failure_failure
is not None:
70 if self.
_error_error
is not None:
72 stream.write(
'</testcase>\n')
74 def _print_error(self, stream, tagname, error):
75 """Print information from a failure or error to the supplied stream."""
76 text = escape(str(error[1]))
78 stream.write(
' <%s type="%s">%s\n' \
79 % (tagname, _clsname(error[0]), text))
80 tb_stream = StringIO()
81 traceback.print_tb(error[2],
None, tb_stream)
82 stream.write(escape(tb_stream.getvalue()))
83 stream.write(
' </%s>\n' % tagname)
88 return cls.__module__ +
"." + cls.__name__
93 """A test result class that stores result as XML.
95 Used by XMLTestRunner.
100 unittest.TestResult.__init__(self)
108 unittest.TestResult.startTest(self, test)
114 time_taken = time.time() - self.
_start_time_start_time
115 unittest.TestResult.stopTest(self, test)
117 info = _TestInfo.create_error(test, time_taken, self.
_error_error)
119 info = _TestInfo.create_failure(test, time_taken, self.
_failure_failure)
121 info = _TestInfo.create_success(test, time_taken)
122 self.
_tests_tests.append(info)
125 unittest.TestResult.addError(self, test, err)
129 unittest.TestResult.addFailure(self, test, err)
133 """Prints the XML report to the supplied stream.
135 The time the tests took to perform as well as the captured standard
136 output and standard error streams must be passed in.a
139 stream.write(
'<testsuite errors="%(e)d" failures="%(f)d" ' % \
140 {
"e": len(self.errors),
"f": len(self.failures) })
141 stream.write(
'name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \
147 for info
in self.
_tests_tests:
148 info.print_report(stream)
149 stream.write(
' <system-out><![CDATA[%s]]></system-out>\n' % out)
150 stream.write(
' <system-err><![CDATA[%s]]></system-err>\n' % err)
151 stream.write(
'</testsuite>\n')
156 """A test runner that stores results in XML format compatible with JUnit.
158 XMLTestRunner(stream=None) -> XML test runner
160 The XML file is written to the supplied stream. If stream is None, the
161 results are stored in a file called TEST-<module>.<class>.xml in the
162 current working directory (if not overridden with the path property),
163 where <module> and <class> are the module and class name of the test class.
169 self.
_path_path =
"."
172 """Run the given test case or test suite."""
173 class_ = test.__class__
174 classname = class_.__module__ +
"." + class_.__name__
175 if self.
_stream_stream ==
None:
176 filename =
"TEST-%s.xml" % classname
177 stream =
file(os.path.join(self.
_path_path, filename),
"w")
178 stream.write(
'<?xml version="1.0" encoding="utf-8"?>\n')
183 start_time = time.time()
188 sys.stdout = StringIO()
189 sys.stderr = StringIO()
192 out_s = sys.stdout.getvalue()
193 except AttributeError:
196 err_s = sys.stderr.getvalue()
197 except AttributeError:
204 time_taken = time.time() - start_time
205 result.print_report(stream, time_taken, out_s, err_s)
206 if self.
_stream_stream
is None:
211 def _set_path(self, path):
212 self.
_path_path = path
214 path = property(
lambda self: self.
_path_path, _set_path,
None,
215 """The path where the XML files are stored.
217 This property is ignored when the XML file is written to a file
226 sys.stdout = StringIO()
227 sys.stderr = StringIO()
237 self.
_stream_stream = StringIO()
239 def _try_test_run(self, test_class, expected):
241 """Run the test suite against the supplied test class and compare the
242 XML result against the expected XML string. Fail if the expected
243 string doesn't match the actual string. All time attributes in the
244 expected string should have the value "0.000". All error and failure
245 messages are reduced to "Foobar".
250 runner.run(unittest.makeSuite(test_class))
252 got = self.
_stream_stream.getvalue()
255 got = re.sub(
r'time="\d+\.\d+"',
'time="0.000"', got)
258 got = re.sub(
r'(?s)<failure (.*?)>.*?</failure>',
r'<failure \1>Foobar</failure>', got)
259 got = re.sub(
r'(?s)<error (.*?)>.*?</error>',
r'<error \1>Foobar</error>', got)
261 got = got.replace(
'type="builtins.',
'type="exceptions.')
263 self.assertEqual(expected, got)
266 """Regression test: Check whether a test run without any tests
267 matches a previous run.
270 class TestTest(unittest.TestCase):
272 self._try_test_run(TestTest,
"""<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="0" time="0.000">
273 <system-out><![CDATA[]]></system-out>
274 <system-err><![CDATA[]]></system-err>
279 """Regression test: Check whether a test run with a successful test
280 matches a previous run.
283 class TestTest(unittest.TestCase):
286 self._try_test_run(TestTest,
"""<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
287 <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
288 <system-out><![CDATA[]]></system-out>
289 <system-err><![CDATA[]]></system-err>
294 """Regression test: Check whether a test run with a failing test
295 matches a previous run.
298 class TestTest(unittest.TestCase):
300 self.assertTrue(
False)
301 self.
_try_test_run_try_test_run(TestTest,
"""<testsuite errors="0" failures="1" name="unittest.TestSuite" tests="1" time="0.000">
302 <testcase classname="__main__.TestTest" name="test_foo" time="0.000">
303 <failure type="exceptions.AssertionError">Foobar</failure>
305 <system-out><![CDATA[]]></system-out>
306 <system-err><![CDATA[]]></system-err>
311 """Regression test: Check whether a test run with a erroneous test
312 matches a previous run.
315 class TestTest(unittest.TestCase):
318 self.
_try_test_run_try_test_run(TestTest,
"""<testsuite errors="1" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
319 <testcase classname="__main__.TestTest" name="test_foo" time="0.000">
320 <error type="exceptions.IndexError">Foobar</error>
322 <system-out><![CDATA[]]></system-out>
323 <system-err><![CDATA[]]></system-err>
328 """Regression test: Check whether a test run with output to stdout
329 matches a previous run.
332 class TestTest(unittest.TestCase):
334 sys.stdout.write(
"Test\n")
335 self.
_try_test_run_try_test_run(TestTest,
"""<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
336 <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
337 <system-out><![CDATA[Test
339 <system-err><![CDATA[]]></system-err>
344 """Regression test: Check whether a test run with output to stderr
345 matches a previous run.
348 class TestTest(unittest.TestCase):
350 sys.stderr.write(
"Test\n")
351 self.
_try_test_run_try_test_run(TestTest,
"""<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
352 <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
353 <system-out><![CDATA[]]></system-out>
354 <system-err><![CDATA[Test
360 """A file-like object that discards everything written to it."""
365 """Check whether the XMLTestRunner recovers gracefully from unit tests
366 that change stdout, but don't change it back properly.
369 class TestTest(unittest.TestCase):
371 sys.stdout = XMLTestRunnerTest.NullStream()
373 runner = XMLTestRunner(self._stream)
374 runner.run(unittest.makeSuite(TestTest))
377 """Check whether the XMLTestRunner recovers gracefully from unit tests
378 that change stderr, but don't change it back properly.
381 class TestTest(unittest.TestCase):
386 runner.run(unittest.makeSuite(TestTest))
389 if __name__ ==
"__main__":
def create_success(test, time)
def create_failure(test, time, failure)
def create_error(test, time, error)
def __init__(self, test, time)
def _print_error(self, stream, tagname, error)
def print_report(self, stream)
def print_report(self, stream, time_taken, out, err)
def addError(self, test, err)
def __init__(self, classname)
def startTest(self, test)
def addFailure(self, test, err)
def __exit__(self, exc_type, exc_val, exc_tb)
def __init__(self, stream=None)
def test_stdout_capture(self)
def test_unittests_changing_stderr(self)
def test_stderr_capture(self)
def _try_test_run(self, test_class, expected)
def test_unittests_changing_stdout(self)