2 Format unittest results in Test Anything Protocol (TAP).
3 https://testanything.org/tap-version-13-specification.html
6 anatoly techtonik <techtonik@gmail.com>
9 0.3 - fixed used imports that failed on Python 2.6
10 0.2 - removed unused import that failed on Python 2.6
17 from unittest
import TextTestRunner
20 from unittest
import TextTestResult
23 from unittest
import _TextTestResult
as TextTestResult
26 class TAPTestResult(TextTestResult
):
27 def _process(self
, test
, msg
, failtype
=None, directive
=None) -> None:
28 """increase the counter, format and output TAP info"""
29 # counterhack: increase test counter
30 test
.suite
.tap_counter
+= 1
31 msg
= "%s %d" % (msg
, test
.suite
.tap_counter
)
34 self
.stream
.write(f
"{msg} - ")
36 self
.stream
.write(f
"{failtype} - ")
37 self
.stream
.write(f
"{test.__class__.__name__}")
38 self
.stream
.write(f
".{test._testMethodName}")
40 self
.stream
.write(directive
)
41 self
.stream
.write("\n")
42 # [ ] write test __doc__ (if exists) in comment
45 def addSuccess(self
, test
) -> None:
46 super().addSuccess(test
)
47 self
._process
(test
, "ok")
49 def addFailure(self
, test
, err
) -> None:
50 super().addFailure(test
, err
)
51 self
._process
(test
, "not ok", "FAIL")
52 # [ ] add structured data about assertion
54 def addError(self
, test
, err
) -> None:
55 super().addError(test
, err
)
56 self
._process
(test
, "not ok", "ERROR")
57 # [ ] add structured data about exception
59 def addSkip(self
, test
, reason
) -> None:
60 super().addSkip(test
, reason
)
61 self
._process
(test
, "ok", directive
=f
" # SKIP {reason}")
63 def addExpectedFailure(self
, test
, err
) -> None:
64 super().addExpectedFailure(test
, err
)
65 self
._process
(test
, "not ok", directive
=" # TODO")
67 def addUnexpectedSuccess(self
, test
) -> None:
68 super().addUnexpectedSuccess(test
)
69 self
._process
(test
, "not ok", "FAIL (unexpected success)")
72 def printErrors(self):
73 def printErrorList(self, flavour, errors):
77 class TAPTestRunner(TextTestRunner
):
78 resultclass
= TAPTestResult
81 self
.stream
.write("TAP version 13\n")
82 # [ ] add commented block with test suite __doc__
83 # [ ] check call with a single test
84 # if isinstance(test, suite.TestSuite):
85 self
.stream
.write(f
"1..{len(list(test))}\n")
87 # counterhack: inject test counter into test suite
89 # counterhack: inject reference to suite into each test case
93 return super().run(test
)
96 if __name__
== "__main__":
100 class Test(unittest
.TestCase
):
101 def test_ok(self
) -> None:
104 def test_fail(self
) -> None:
105 self
.assertTrue(False)
107 def test_error(self
) -> None:
110 @unittest.skip("skipin'")
111 def test_skip(self
) -> None:
114 @unittest.expectedFailure
115 def test_not_ready(self
) -> None:
118 @unittest.expectedFailure
119 def test_invalid_fail_mark(self
) -> None:
122 def test_another_ok(self
) -> None:
125 suite
= unittest
.TestSuite(
131 Test('test_not_ready'),
132 Test('test_invalid_fail_mark'),
133 Test('test_another_ok'),
136 if not TAPTestRunner().run(suite
).wasSuccessful():