python测试框架nose,在jenkins打印allure报告,界面友好,但是欠缺输出功能,为了以后排查问题使用,故修改源码
直接上源码:nose_allure中__init__.py
同时也需要修改:allure中common.py代码
里面有个bug是,输出的内容没有换行,很不友好,如果哪位大牛修改了请通知小弟。
输出的页面是: ps:qq截图粘帖图片后,提交了就没有了呢,
common.py 修改stop_case:代码如下
def stop_case(self, status, message=None, trace=None,description=None): """ :arg status: one of :py:class:`allure.constants.Status` :arg message: error message from the test :arg trace: error trace from the test Finalizes with important data the test at the top of ``self.stack`` and returns it If either ``message`` or ``trace`` are given adds a ``Failure`` object to the test with them. """ test = self.stack[-1] test.status = status test.stop = now() if description: test.description = description else: test.description='' if message or trace: test.failure = Failure(message=message, trace=trace or '') self.testsuite.tests.append(test) return test
nose_allure中__init__.py代码修改如下,修改内容自己比对
# -*- coding: utf-8 -*- __author__ = "chipiga86@gmail.com" import os import traceback from types import ModuleType from functools import wraps import sys import nose from allure.constants import Status, Label from nose.plugins.base import Plugin from nose.plugins.attrib import AttributeSelector from nose.plugins.plugintest import MultiProcessFile from .utils import AllureWrapper, get_labels from io import StringIO from nose.pyversion import force_unicode, format_exception def run_only_when_suite_exist(func): """ Decorator to disable method if allure doesnt have any active suites. This is needed to avoid of IndexError that hides the real exception. """ @wraps(func) def wrapper(self, *args, **kwargs): if self.allure.impl.stack: return func(self, *args, **kwargs) return wrapper class Tee(object): def __init__(self, encoding, *args): self._encoding = encoding self._streams = args def write(self, data): data = force_unicode(data, self._encoding) for s in self._streams: s.write(data) def writelines(self, lines): for line in lines: self.write(line) def flush(self): for s in self._streams: s.flush() def isatty(self): return False class Allure(Plugin): # name = 'xunit' # score = 1500 encoding = 'UTF-8' # error_report_file = None test_suite = False def __init__(self): super(Allure, self).__init__() self._capture_stack = [] self._currentStdout = None self._currentStderr = None def getvalue(self): print() def options(self, parser, env): super(Allure, self).options(parser, env) parser.add_option('--logdir', dest='logdir') parser.add_option('--not-clear-logdir', dest='not_clear_logdir', action='store_true', default=False) parser.add_option('--feature', dest='feature') parser.add_option('--story', dest='story') parser.add_option('--issue', dest='issue') parser.add_option('--severity', dest='severity') def configure(self, options, conf): super(Allure, self).configure(options, conf) self.options = options if options.logdir: logdir = os.path.normpath(os.path.abspath(os.path.expanduser( os.path.expandvars(options.logdir)))) if not os.path.isdir(logdir): os.makedirs(logdir) else: # Need to provide an option to skip dir cleaning due to multiprocess # plugin usage can lead to logdir cleaning at the end of testing. # Unfortunately not possible to detect is it child process or parent. # Otherwise possible to clean logdir only in parent process always. if not options.not_clear_logdir: for file_name in os.listdir(logdir): file_path = os.path.join(logdir, file_name) if os.path.isfile(file_path): os.unlink(file_path) self.allure = nose.allure = AllureWrapper(logdir) for label in 'feature', 'story', 'issue', 'severity': if getattr(options, label, None): if not getattr(options, 'attr', None): options.attr = [] get_attr = lambda l: "%s_%s=%s" % \ (Label.DEFAULT, getattr(Label, label.upper()), l.strip()) attrs = map(get_attr, getattr(options, label).split(',')) options.attr.extend(attrs) if options.attr: for plugin in self.conf.plugins.plugins: if isinstance(plugin, AttributeSelector): plugin.configure(options, conf) break def begin(self): if not self.conf.options.logdir: raise LookupError('Should provide "--logdir" argument!') def startContext(self, context): self._startCapture() def stopContext(self, context): self._endCapture() def beforeTest(self, test): """Initializes a timer before starting a test.""" self._startCapture() def _endCapture(self): if self._capture_stack: sys.stdout, sys.stderr = self._capture_stack.pop() def afterTest(self, test): self._endCapture() self._currentStdout = None self._currentStderr = None def _startCapture(self): self._capture_stack.append((sys.stdout, sys.stderr)) self._currentStdout = StringIO() self._currentStderr = StringIO() sys.stdout = Tee(self.encoding, self._currentStdout, sys.stdout) sys.stderr = Tee(self.encoding, self._currentStderr, sys.stderr) def _getCapturedStdout(self): if self._currentStdout: value = self._currentStdout.getvalue() return value return '' def startTest(self, test): if not self.test_suite: context_name = getattr(test.context, '__module__', test.context.__name__) self.allure.impl.start_suite(name=context_name, description=test.context.__doc__ or None) self.test_suite = True if hasattr(test.test, "test"): method = test.test.test else: method = getattr(test.test, test.test._testMethodName) hierarchy = ".".join(filter(None, test.address()[1:])) self.allure.impl.start_case(hierarchy,labels=get_labels(method)) @run_only_when_suite_exist def stopTest(self, test): # if we running in multiprocess mode we should trigger suite closing # each time when we exiting test if self.options.multiprocess_workers: self.allure.impl.stop_suite() self.test_suite = False @run_only_when_suite_exist def stopContext(self, context): # if we running not in multiprocess mode we should trigger suite # closing only when exiting context if not self.options.multiprocess_workers and self.test_suite and \ isinstance(context, ModuleType): self.allure.impl.stop_suite() self.test_suite = False @run_only_when_suite_exist def addError(self, test, err): message, trace = self._parse_tb(err) self.allure.impl.stop_case(Status.BROKEN, message=message, trace=trace) @run_only_when_suite_exist def addFailure(self, test, err): message, trace = self._parse_tb(err) if hasattr(test.test, "test"): method = test.test.test else: method = getattr(test.test, test.test._testMethodName) hierarchy = ".".join(filter(None, test.address()[1:])) description=self._getCapturedStdout() self.allure.impl.stop_case(Status.FAILED, message=message, trace=trace,description='描述:'+method.__doc__+' 输出:'+description) @run_only_when_suite_exist def addSuccess(self, test): if hasattr(test.test, "test"): method = test.test.test else: method = getattr(test.test, test.test._testMethodName) hierarchy = ".".join(filter(None, test.address()[1:])) description=self._getCapturedStdout() self.allure.impl.stop_case(Status.PASSED,description='描述:'+method.__doc__+' 输出:'+description) @run_only_when_suite_exist def addSkip(self, test): self.allure.impl.stop_case(Status.CANCELED) def finalize(self, result): self.allure.impl.store_environment() @staticmethod def _parse_tb(trace): # message = ''.join( # traceback.format_exception_only(trace[0], trace[1])).strip() # trace = ''.join(traceback.format_exception(*trace)).strip() # return message, trace exc_type, exc_val, tb = trace # message = exc_message(trace) message = ''.join( traceback.format_exception_only(exc_type, exc_val if isinstance(exc_val, exc_type) else exc_type(exc_val))).strip() trace = ''.join(traceback.format_exception( exc_type, exc_val if isinstance(exc_val, exc_type) else exc_type(exc_val), tb )) return message, trace def exc_message(exc_info): """Return the exception's message.""" exc = exc_info[1] if exc is None: # str exception result = exc_info[0] else: try: result = str(exc) except UnicodeEncodeError: try: result = unicode(exc) # flake8: noqa except UnicodeError: # Fallback to args as neither str nor # unicode(Exception(u'\xe6')) work in Python < 2.6 result = exc.args[0] return result