2 XML Test Runner for PyUnit
16 from xml.sax.saxutils
import escape
19 from StringIO
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)
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,
70 if self.
_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)
115 unittest.TestResult.stopTest(self, test)
117 info = _TestInfo.create_error(test, time_taken, self.
_error)
119 info = _TestInfo.create_failure(test, time_taken, self.
_failure)
121 info = _TestInfo.create_success(test, time_taken)
122 self._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' % \
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.
172 """Run the given test case or test suite."""
173 class_ = test.__class__
174 classname = class_.__module__ +
"." + class_.__name__
176 filename =
"TEST-%s.xml" % classname
177 stream = file(os.path.join(self.
_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)
211 def _set_path(self, path):
214 path = property(
lambda self: self.
_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()
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.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):
301 self.
_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(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(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(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 test_unittests_changing_stderr
def test_unittests_changing_stdout