栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

python unittest+BSTestRunner生成测试报告与unittest执行测试用例封装

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

python unittest+BSTestRunner生成测试报告与unittest执行测试用例封装

python unittest+BSTestRunner生成测试报告与unittest执行测试用例封装

参考框架时unittest与pytest对比后,思考还是用unittest来做接口自动化框架,但是unittest内不带html的报告,需要添加报告模块,网上有HtmlTestRunner也是基于runner来开发的,但后来又找到了另一个html的模块BSTestRunner新建python.py文件,把代码粘贴在内,放在lib/python/site-packages的根目录下

"""
A TestRunner for use with the Python unit testing framework. It generates a HTML report to show the result at a glance.

The simplest way to use this is to invoke its main method. E.g.

    import unittest
    import BSTestRunner

    ... define your tests ...

    if __name__ == '__main__':
        BSTestRunner.main()


For more customization options, instantiates a BSTestRunner object.
BSTestRunner is a counterpart to unittest's TextTestRunner. E.g.

    # output to a file
    fp = file('my_report.html', 'wb')
    runner = BSTestRunner.BSTestRunner(
                stream=fp,
                title='My unit test',
                description='This demonstrates the report output by BSTestRunner.'
                )

    # Use an external stylesheet.
    # See the Template_mixin class for more customizable options
    runner.STYLESHEET_TMPL = ''

    # run the test
    runner.run(my_test_suite)


------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
Copyright (c) 2016, Eason Han
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND ConTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR ConTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR ConSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""


__author__ = "Wai Yip Tung && Eason Han"
__version__ = "0.8.4"


"""
Change History

Version 0.8.3
* Modify html style using bootstrap3.

Version 0.8.3
* Prevent crash on class or module-level exceptions (Darren Wurf).

Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).

Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.

Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat 

%(heading)s %(report)s %(ending)s
""" # variables: (title, generator, stylesheet, heading, report, ending) # ------------------------------------------------------------------------ # Stylesheet # # alternatively use a for external style sheet, e.g. # STYLESHEET_TMPL = """ """ # ------------------------------------------------------------------------ # Heading # HEADING_TMPL = """

%(title)s

%(parameters)s

%(description)s

""" # variables: (title, parameters, description) HEADING_ATTRIBUTE_TMPL = """

%(name)s: %(value)s

""" # variables: (name, value) # ------------------------------------------------------------------------ # Report # REPORT_TMPL = """

Summary Failed All

%(test_list)s
Test Group/Test case Count Pass Fail Error View
Total %(count)s %(Pass)s %(fail)s %(error)s  
""" # variables: (test_list, count, Pass, fail, error) REPORT_CLASS_TMPL = r""" %(desc)s %(count)s %(Pass)s %(fail)s %(error)s Detail """ # variables: (style, desc, count, Pass, fail, error, cid) REPORT_TEST_WITH_OUTPUT_TMPL = r"""
%(desc)s
%(status)s """ # variables: (tid, Class, style, desc, status) REPORT_TEST_NO_OUTPUT_TMPL = r"""
%(desc)s
%(status)s """ # variables: (tid, Class, style, desc, status) REPORT_TEST_OUTPUT_TMPL = r""" %(id)s: %(output)s """ # variables: (id, output) # ------------------------------------------------------------------------ # ENDING # ENDING_TMPL = """
 
""" # -------------------- The end of the Template class ------------------- TestResult = unittest.TestResult class _TestResult(TestResult): # note: _TestResult is a pure representation of results. # It lacks the output and reporting ability compares to unittest._TextTestResult. def __init__(self, verbosity=1): TestResult.__init__(self) self.outputBuffer = StringIO() self.stdout0 = None self.stderr0 = None self.success_count = 0 self.failure_count = 0 self.error_count = 0 self.verbosity = verbosity # result is a list of result in 4 tuple # ( # result code (0: success; 1: fail; 2: error), # TestCase object, # Test output (byte string), # stack trace, # ) self.result = [] def startTest(self, test): TestResult.startTest(self, test) # just one buffer for both stdout and stderr stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.stdout0 = sys.stdout self.stderr0 = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector def complete_output(self): """ Disconnect output redirection and return buffer. Safe to call multiple times. """ if self.stdout0: sys.stdout = self.stdout0 sys.stderr = self.stderr0 self.stdout0 = None self.stderr0 = None return self.outputBuffer.getvalue() def stopTest(self, test): # Usually one of addSuccess, addError or addFailure would have been called. # But there are some path in unittest that would bypass this. # We must disconnect stdout in stopTest(), which is guaranteed to be called. self.complete_output() def addSuccess(self, test): self.success_count += 1 TestResult.addSuccess(self, test) output = self.complete_output() self.result.append((0, test, output, '')) if self.verbosity > 1: sys.stderr.write('ok ') sys.stderr.write(str(test)) sys.stderr.write('n') else: sys.stderr.write('../../TestLoader/TestCase') def addError(self, test, err): self.error_count += 1 TestResult.addError(self, test, err) _, _exc_str = self.errors[-1] output = self.complete_output() self.result.append((2, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('E ') sys.stderr.write(str(test)) sys.stderr.write('n') else: sys.stderr.write('E') def addFailure(self, test, err): self.failure_count += 1 TestResult.addFailure(self, test, err) _, _exc_str = self.failures[-1] output = self.complete_output() self.result.append((1, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(test)) sys.stderr.write('n') else: sys.stderr.write('F') class BSTestRunner(Template_mixin): """ """ def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None): self.stream = stream self.verbosity = verbosity if title is None: self.title = self.DEFAULT_TITLE else: self.title = title if description is None: self.description = self.DEFAULT_DEscriptION else: self.description = description self.startTime = datetime.datetime.now() def run(self, test): "Run the given test case or test suite." result = _TestResult(self.verbosity) test(result) self.stopTime = datetime.datetime.now() self.generateReport(test, result) # print >>sys.stderr, 'nTime Elapsed: %s' % (self.stopTime-self.startTime) sys.stderr.write('nTime Elapsed: %s' % (self.stopTime-self.startTime)) return result def sortResult(self, result_list): # unittest does not seems to run in any particular order. # Here at least we want to group them together by class. rmap = {} classes = [] for n,t,o,e in result_list: cls = t.__class__ # if not rmap.has_key(cls): if not cls in rmap: rmap[cls] = [] classes.append(cls) rmap[cls].append((n,t,o,e)) r = [(cls, rmap[cls]) for cls in classes] return r def getReportAttributes(self, result): """ Return report attributes as a list of (name, value). Override this to add custom attributes. """ startTime = str(self.startTime)[:19] duration = str(self.stopTime - self.startTime) status = [] if result.success_count: status.append('Pass %s' % result.success_count) if result.failure_count: status.append('Failure %s' % result.failure_count) if result.error_count: status.append('Error %s' % result.error_count ) if status: status = ' '.join(status) else: status = 'none' return [ ('Start Time', startTime), ('Duration', duration), ('Status', status), ] def generateReport(self, test, result): report_attrs = self.getReportAttributes(result) generator = 'BSTestRunner %s' % __version__ stylesheet = self._generate_stylesheet() heading = self._generate_heading(report_attrs) report = self._generate_report(result) ending = self._generate_ending() output = self.HTML_TMPL % dict( title = saxutils.escape(self.title), generator = generator, stylesheet = stylesheet, heading = heading, report = report, ending = ending, ) try: self.stream.write(output.encode('utf8')) except: self.stream.write(output) def _generate_stylesheet(self): return self.STYLESHEET_TMPL def _generate_heading(self, report_attrs): a_lines = [] for name, value in report_attrs: line = self.HEADING_ATTRIBUTE_TMPL % dict( # name = saxutils.escape(name), # value = saxutils.escape(value), name = name, value = value, ) a_lines.append(line) heading = self.HEADING_TMPL % dict( title = saxutils.escape(self.title), parameters = ''.join(a_lines), description = saxutils.escape(self.description), ) return heading def _generate_report(self, result): rows = [] sortedResult = self.sortResult(result.result) for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class np = nf = ne = 0 for n,t,o,e in cls_results: if n == 0: np += 1 elif n == 1: nf += 1 else: ne += 1 # format class description if cls.__module__ == "__main__": name = cls.__name__ else: name = "%s.%s" % (cls.__module__, cls.__name__) doc = cls.__doc__ and cls.__doc__.split("n")[0] or "" desc = doc and '%s: %s' % (name, doc) or name row = self.REPORT_CLASS_TMPL % dict( style = ne > 0 and 'text text-warning' or nf > 0 and 'text text-danger' or 'text text-success', desc = desc, count = np+nf+ne, Pass = np, fail = nf, error = ne, cid = 'c%s' % (cid+1), ) rows.append(row) for tid, (n,t,o,e) in enumerate(cls_results): self._generate_report_test(rows, cid, tid, n, t, o, e) report = self.REPORT_TMPL % dict( test_list = ''.join(rows), count = str(result.success_count+result.failure_count+result.error_count), Pass = str(result.success_count), fail = str(result.failure_count), error = str(result.error_count), ) return report def _generate_report_test(self, rows, cid, tid, n, t, o, e): # e.g. 'pt1.1', 'ft1.1', etc has_output = bool(o or e) tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1) name = t.id().split('.')[-1] doc = t.shortDescription() or "" desc = doc and ('%s: %s' % (name, doc)) or name tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL # o and e should be byte string because they are collected from stdout and stderr? if isinstance(o,str): # TODO: some problem with 'string_escape': it escape n and mess up formating # uo = unicode(o.encode('string_escape')) try: uo = o.decode('latin-1') except: uo = o else: uo = o if isinstance(e,str): # TODO: some problem with 'string_escape': it escape n and mess up formating # ue = unicode(e.encode('string_escape')) try: ue = e.decode('latin-1') except: ue = e else: ue = e script = self.REPORT_TEST_OUTPUT_TMPL % dict( id = tid, output = saxutils.escape(uo+ue), ) row = tmpl % dict( tid = tid, # Class = (n == 0 and 'hiddenRow' or 'none'), Class = (n == 0 and 'hiddenRow' or 'text text-success'), # style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'), style = n == 2 and 'text text-warning' or (n == 1 and 'text text-danger' or 'text text-success'), desc = desc, script = script, status = self.STATUS[n], ) rows.append(row) if not has_output: return def _generate_ending(self): return self.ENDING_TMPL ############################################################################## # Facilities for running tests from the command line ############################################################################## # Note: Reuse unittest.TestProgram to launch test. In the future we may # build our own launcher to support more specific command line # parameters like test title, CSS, etc. class TestProgram(unittest.TestProgram): """ A variation of the unittest.TestProgram. Please refer to the base class for command line parameters. """ def runTests(self): # Pick BSTestRunner as the default test runner. # base class's testRunner parameter is not useful because it means # we have to instantiate BSTestRunner before we know self.verbosity. if self.testRunner is None: self.testRunner = BSTestRunner(verbosity=self.verbosity) unittest.TestProgram.runTests(self) main = TestProgram ############################################################################## # Executing this module from the command line ############################################################################## if __name__ == "__main__": main(module=None)

执行main()方法查看
unittest执行测试case有三种方式
第一种 unittest.main()方法 执行当前py文件内所有case

# -*- coding: utf-8 -*-
import unittest
from TestLoader.TestCase.methods.spring_festival import SpringFestivalMethods
from backstage.account_access import AccountAccess
from backstage.activity_general import AbnormalCase


class test_sample(unittest.TestCase):

    def __init__(self, *args, **kwargs):
        super(test_sample, self).__init__(*args, **kwargs)
        self.A = 107205623
        self.code = 692
        self.lucky_attrs = {
            "lotteryType": 1,
            "lotteryFlag": 1
        }
        self.physical_attrs = {
            "count": 1
        }
        self.attrs = {
            "costType": 1,
            "count": 10
        }

    def test_case_01(self):
        """页面信息"""
        response = SpringFestivalMethods().getLotteryInfo(self.A)

    def test_case_02(self):
        """刷新气泡"""
        response = SpringFestivalMethods().refreshUnRecvCount(self.A)

    def test_case_03(self):
        """实验抽奖异常case脚本"""
        AccountAccess().add_ticket(self.A)
        AbnormalCase(self.A, self.code, "lottery", **self.lucky_attrs).run(1)

    def test_case_04(self):
        """实验体力异常case脚本"""
        AbnormalCase(self.A, self.code, "diamonChange", **self.physical_attrs).run(2)

    def test_case_05(self):
        """实验体力异常case脚本"""
        AbnormalCase(self.A, 544, "exchangeConpon", **self.attrs).run(3)


if __name__ == '__main__':
    unittest.main()

第二种:执行测试用例文件
新建run.py文件写入执行case方法

用遍历list的方式执行多个py文件,在方法内加入生成测试报告方法BSTestRunner()
返回的result在后续的钉钉机器人处理时会用到参数

def run_py(list_file):
    """执行多个测试用例文件"""
    test_dir = '../TestLoader'
    report_dir = './report'
    for i in list_file:
        discover = unittest.defaultTestLoader.discover(test_dir, pattern=i)
        # 定义报告格式
        now = time.strftime('%Y-%m-%d %H_%M_%S')
        report_name = report_dir + '/' + now + 'test_report.html'
        # 运行用例并生成测试报告
        with open(report_name, 'wb') as f:
            runner = BSTestRunner(stream=f, title="测试case内容", description="测试报告")
            result = runner.run(discover)
    logger.info("n=====生成测试报告=====")
    return result


if __name__ == '__main__':
    file_list = ["test_shiyan.py", "MeetSpringFestival.py"]
    run_py(file_list)

第三种:执行文件内的单独case

def run_case(test_py, list_case):
    """执行一个文件下指定case内容"""
    report_dir = './report'
    # 1、构造用例集
    suite = unittest.TestSuite()
    # 2、执行顺序是安加载顺序:先执行test_case_01,再执行test_case_02
    for i in list_case:
        suite.addTest(test_py(i))
    # 定义报告格式
    now = time.strftime('%Y-%m-%d %H_%M_%S')
    report_name = report_dir + '/' + now + 'test_report.html'
    # 运行用例并生成测试报告
    with open(report_name, 'wb') as f:
        runner = BSTestRunner(stream=f, title="测试case内容", description="测试报告")
        result = runner.run(suite)
    logger.info("n=====生成测试报告=====")
    return result


if __name__ == '__main__':
    case_list = ["test_case_01", "test_case_02"]
    # run_case()第一个参数是py文件的名称,第二个是case的list
    run_case(test_sample, case_list)

执行后,测试报告会保存在方法内定义的report文件内

html报告展示效果

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/740823.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号