I'm trying to learn a bit about writing nose plugins, so I've taken their example HtmlOutput plugin and I'm trying to use it like so:
import unittest
import nose
import sys
import traceback
class MyTest(unittest.TestCase):
def test_failing(self):
self.assertTrue(False)
def test_passing(self):
self.assertTrue(True)
class HtmlOutput(nose.plugins.Plugin):
"""Output test results as ugly, unstyled html.
"""
name = 'html-output'
score = 2 # run late
def __init__(self):
super(HtmlOutput, self).__init__()
self.html = [ '<html><head>',
'<title>Test output</title>',
'</head><body>' ]
def configure(self, options, conf):
super(HtmlOutput, self).configure(options, conf)
self.enabled = True
def addSuccess(self, test):
self.html.append('<span>ok</span>')
def addError(self, test, err):
err = self.formatErr(err)
self.html.append('<span>ERROR</span>')
self.html.append('<pre>%s</pre>' % err)
def addFailure(self, test, err):
err = self.formatErr(err)
self.html.append('<span>FAIL</span>')
self.html.append('<pre>%s</pre>' % err)
def finalize(self, result):
self.html.append('<div>')
self.html.append("Ran %d test%s" %
(result.testsRun, result.testsRun != 1 and "s" or ""))
self.html.append('</div>')
self.html.append('<div>')
if not result.wasSuccessful():
self.html.extend(['<span>FAILED ( ',
'failures=%d ' % len(result.failures),
'errors=%d' % len(result.errors),
')</span>'])
else:
self.html.append('OK')
self.html.append('</div></body></html>')
# print >> sys.stderr, self.html
for l in self.html:
self.stream.writeln(l)
def formatErr(self, err):
exctype, value, tb = err
return ''.join(traceback.format_exception(exctype, value, tb))
def setOutputStream(self, stream):
# grab for own use
self.stream = stream
# return dummy stream
class dummy:
def write(self, *arg):
pass
def writeln(self, *arg):
pass
d = dummy()
return d
def startContext(self, ctx):
try:
n = ctx.__name__
except AttributeError:
n = str(ctx).replace('<', '').replace('>', '')
self.html.extend(['<fieldset>', '<legend>', n, '</legend>'])
try:
path = ctx.__file__.replace('.pyc', '.py')
self.html.extend(['<div>', path, '</div>'])
except AttributeError:
pass
def stopContext(self, ctx):
self.html.append('</fieldset>')
def startTest(self, test):
self.html.extend([ '<div><span>',
test.shortDescription() or str(test),
'</span>' ])
def stopTest(self, test):
self.html.append('</div>')
suite = unittest.defaultTestLoader.loadTestsFromTestCase(MyTest)
nose.run(argv=['-s'],
suite=suite,
addplugins=[HtmlOutput()])
The plugin appears to actually initialize so I get this output:
<html><head>
<title>Test output</title>
</head><body>
<div>
Ran 2 tests
</div>
<div>
<span>FAILED (
failures=1
errors=0
)</span>
</div></body></html>
The problem I'm having is the plugin appears to totally skip over the addFailure
, addSuccess
, and addError
methods completely and I cannot track down any reason for it to be skipping them.
After doing additional research today, I came across this GitHub issue in the nose repo.
The problem was unittest.defaultTestLoader.loadTestsFromTestCase(MyTest)
does in fact return a TestSuite
, and suite
option appears to actually want a list of tests. Fortunately, fixing my problem was as easy as doing suite._tests
and the addFailure
et al hooks work as expected.