HTMLTestRunner

   1 #-*- coding: utf-8 -*-
   2 """
   3 A TestRunner for use with the Python unit testing framework. It
   4 generates a HTML report to show the result at a glance.
   5 
   6 The simplest way to use this is to invoke its main method. E.g.
   7 
   8     import unittest
   9     import HTMLTestRunner
  10 
  11     ... define your tests ...
  12 
  13     if __name__ == '__main__':
  14         HTMLTestRunner.main()
  15 
  16 
  17 For more customization options, instantiates a HTMLTestRunner object.
  18 HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
  19 
  20     # output to a file
  21     fp = file('my_report.html', 'wb')
  22     runner = HTMLTestRunner.HTMLTestRunner(
  23                 stream=fp,
  24                 title='My unit test',
  25                 description='This demonstrates the report output by HTMLTestRunner.'
  26                 )
  27 
  28     # Use an external stylesheet.
  29     # See the Template_mixin class for more customizable options
  30     runner.STYLESHEET_TMPL = ''
  31 
  32     # run the test
  33     runner.run(my_test_suite)
  34 
  35 
  36 ------------------------------------------------------------------------
  37 Copyright (c) 2004-2007, Wai Yip Tung
  38 All rights reserved.
  39 
  40 Redistribution and use in source and binary forms, with or without
  41 modification, are permitted provided that the following conditions are
  42 met:
  43 
  44 * Redistributions of source code must retain the above copyright notice,
  45   this list of conditions and the following disclaimer.
  46 * Redistributions in binary form must reproduce the above copyright
  47   notice, this list of conditions and the following disclaimer in the
  48   documentation and/or other materials provided with the distribution.
  49 * Neither the name Wai Yip Tung nor the names of its contributors may be
  50   used to endorse or promote products derived from this software without
  51   specific prior written permission.
  52 
  53 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  54 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  55 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  56 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
  57 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  58 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  59 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  60 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  61 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  62 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  63 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  64 """
  65 
  66 # URL: http://tungwaiyip.info/software/HTMLTestRunner.html
  67 
  68 __author__ = "Wai Yip Tung"
  69 __version__ = "0.8.3"
  70 
  71 
  72 """
  73 Change History
  74 Version 0.8.4 by GoverSky
  75 * Add sopport for 3.x
  76 * Add piechart for resultpiechart
  77 * Add Screenshot for selenium_case test
  78 * Add Retry on failed
  79 
  80 Version 0.8.3
  81 * Prevent crash on class or module-level exceptions (Darren Wurf).
  82 
  83 Version 0.8.2
  84 * Show output inline instead of popup window (Viorel Lupu).
  85 
  86 Version in 0.8.1
  87 * Validated XHTML (Wolfgang Borgert).
  88 * Added description of test classes and test cases.
  89 
  90 Version in 0.8.0
  91 * Define Template_mixin class for customization.
  92 * Workaround a IE 6 bug that it does not treat 
 373 
374
375 376
377
378 %(heading)s 379 %(report)s 380 %(ending)s 381 382 383 384 """ 385 # variables: (title, generator, stylesheet, heading, report, ending) 386 387 388 # ------------------------------------------------------------------------ 389 # Stylesheet 390 # 391 # alternatively use a for external style sheet, e.g. 392 # 393 394 STYLESHEET_TMPL = """ 395 578 """ 579 580 # ------------------------------------------------------------------------ 581 # Heading 582 # 583 584 HEADING_TMPL = """
585

%(title)s

586 %(parameters)s 587

%(description)s

588
589 590 """ # variables: (title, parameters, description) 591 592 HEADING_ATTRIBUTE_TMPL = """

%(name)s: %(value)s

593 """ # variables: (name, value) 594 595 # ------------------------------------------------------------------------ 596 # Report 597 # 598 599 REPORT_TMPL = """ 600

显示 601 概要 602 失败 603 所有 604

605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625%(test_list)s 626 627 628 629 630 631 632 633 634 635
测试组/测试用例 总数 通过 失败 错误 视图 错误截图
统计 %(count)s %(Pass)s %(fail)s %(error)s    
636 640 """ 641 # variables: (test_list, count, Pass, fail, error) 642 643 REPORT_CLASS_TMPL = r""" 644 645 %(desc)s 646 %(count)s 647 %(Pass)s 648 %(fail)s 649 %(error)s 650 详情 651   652 653 """ # variables: (style, desc, count, Pass, fail, error, cid) 654 655 REPORT_TEST_WITH_OUTPUT_TMPL = r""" 656 657
%(desc)s
658 659 660 661 662 663 %(status)s 664 665 674 675 676 677 %(img)s 678 679 """ # variables: (tid, Class, style, desc, status,img) 680 681 REPORT_TEST_NO_OUTPUT_TMPL = r""" 682 683
%(desc)s
684 %(status)s 685 %(img)s 686 687 """ # variables: (tid, Class, style, desc, status,img) 688 689 REPORT_TEST_OUTPUT_TMPL = r""" 690 %(id)s: %(output)s 691 """ # variables: (id, output) 692 693 694 IMG_TMPL = r""" 695 显示截图 696 701 """ 702 # ------------------------------------------------------------------------ 703 # ENDING 704 # 705 706 ENDING_TMPL = """
 
""" 707 708 # -------------------- The end of the Template class ------------------- 709 710 def __getattribute__(self, item): 711 value = object.__getattribute__(self, item) 712 if PY3K: 713 return value 714 else: 715 if isinstance(value, str): 716 return value.decode("utf-8") 717 else: 718 return value 719 720 721 TestResult = unittest.TestResult 722 723 724 class _TestResult(TestResult): 725 # note: _TestResult is a pure representation of results. 726 # It lacks the output and reporting ability compares to unittest._TextTestResult. 727 728 def __init__(self, verbosity=1, retry=0,save_last_try=True): 729 TestResult.__init__(self) 730 self.stdout0 = None 731 self.stderr0 = None 732 self.success_count = 0 733 self.failure_count = 0 734 self.error_count = 0 735 self.verbosity = verbosity 736 737 # result is a list of result in 4 tuple 738 # ( 739 # result code (0: success; 1: fail; 2: error), 740 # TestCase object, 741 # Test output (byte string), 742 # stack trace, 743 # ) 744 self.result = [] 745 self.retry = retry 746 self.trys = 0 747 self.status = 0 748 self.save_last_try = save_last_try 749 self.outputBuffer = StringIO.StringIO() 750 751 def startTest(self, test): 752 test.imgs = [] 753 # test.imgs = getattr(test, "imgs", []) 754 TestResult.startTest(self, test) 755 self.outputBuffer.seek(0) 756 self.outputBuffer.truncate() 757 stdout_redirector.fp = self.outputBuffer 758 stderr_redirector.fp = self.outputBuffer 759 self.stdout0 = sys.stdout 760 self.stderr0 = sys.stderr 761 sys.stdout = stdout_redirector 762 sys.stderr = stderr_redirector 763 764 def complete_output(self): 765 """ 766 Disconnect output redirection and return buffer. 767 Safe to call multiple times. 768 """ 769 if self.stdout0: 770 sys.stdout = self.stdout0 771 sys.stderr = self.stderr0 772 self.stdout0 = None 773 self.stderr0 = None 774 return self.outputBuffer.getvalue() 775 776 def stopTest(self, test): 777 # Usually one of addSuccess, addError or addFailure would have been called. 778 # But there are some path in unittest that would bypass this. 779 # We must disconnect stdout in stopTest(), which is guaranteed to be called. 780 if self.retry: 781 if self.status == 1: 782 self.trys += 1 783 if self.trys <= self.retry: 784 if self.save_last_try: 785 t = self.result.pop(-1) 786 if t[0]==1: 787 self.failure_count-=1 788 else: 789 self.error_count -= 1 790 test=copy.copy(test) 791 sys.stderr.write("Retesting... ") 792 sys.stderr.write(str(test)) 793 sys.stderr.write('..%d \n' % self.trys) 794 doc = test._testMethodDoc or '' 795 if doc.find('_retry')!=-1: 796 doc = doc[:doc.find('_retry')] 797 desc ="%s_retry:%d" %(doc, self.trys) 798 if not PY3K: 799 if isinstance(desc, str): 800 desc = desc.decode("utf-8") 801 test._testMethodDoc = desc 802 test(self) 803 else: 804 self.status = 0 805 self.trys = 0 806 self.complete_output() 807 808 def addSuccess(self, test): 809 self.success_count += 1 810 self.status = 0 811 TestResult.addSuccess(self, test) 812 output = self.complete_output() 813 self.result.append((0, test, output, '')) 814 if self.verbosity > 1: 815 sys.stderr.write('ok ') 816 sys.stderr.write(str(test)) 817 sys.stderr.write('\n') 818 else: 819 sys.stderr.write('.') 820 821 def addError(self, test, err): 822 self.error_count += 1 823 self.status = 1 824 TestResult.addError(self, test, err) 825 _, _exc_str = self.errors[-1] 826 output = self.complete_output() 827 self.result.append((2, test, output, _exc_str)) 828 if not getattr(test, "driver",""): 829 pass 830 else: 831 try: 832 driver = getattr(test, "driver") 833 test.imgs.append(driver.get_screenshot_as_base64()) 834 except Exception: 835 pass 836 if self.verbosity > 1: 837 sys.stderr.write('E ') 838 sys.stderr.write(str(test)) 839 sys.stderr.write('\n') 840 else: 841 sys.stderr.write('E') 842 843 def addFailure(self, test, err): 844 self.failure_count += 1 845 self.status = 1 846 TestResult.addFailure(self, test, err) 847 _, _exc_str = self.failures[-1] 848 output = self.complete_output() 849 self.result.append((1, test, output, _exc_str)) 850 if not getattr(test, "driver",""): 851 pass 852 else: 853 try: 854 driver = getattr(test, "driver") 855 test.imgs.append(driver.get_screenshot_as_base64()) 856 except Exception as e: 857 pass 858 if self.verbosity > 1: 859 sys.stderr.write('F ') 860 sys.stderr.write(str(test)) 861 sys.stderr.write('\n') 862 else: 863 sys.stderr.write('F') 864 865 866 class HTMLTestRunner(Template_mixin): 867 def __init__(self, stream=sys.stdout, verbosity=2, title=None, description=None, retry=0,save_last_try=False): 868 self.stream = stream 869 self.retry = retry 870 self.save_last_try=save_last_try 871 self.verbosity = verbosity 872 if title is None: 873 self.title = self.DEFAULT_TITLE 874 else: 875 self.title = title 876 if description is None: 877 self.description = self.DEFAULT_DESCRIPTION 878 else: 879 self.description = description 880 881 self.startTime = datetime.datetime.now() 882 883 def run(self, test): 884 "Run the given test case or test suite." 885 result = _TestResult(self.verbosity, self.retry, self.save_last_try) 886 test(result) 887 self.stopTime = datetime.datetime.now() 888 self.generateReport(test, result) 889 if PY3K: 890 # for python3 891 # print('\nTime Elapsed: %s' % (self.stopTime - self.startTime),file=sys.stderr) 892 output = '\nTime Elapsed: %s' % (self.stopTime - self.startTime) 893 sys.stderr.write(output) 894 else: 895 print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime - self.startTime) 896 return result 897 898 def sortResult(self, result_list): 899 # unittest does not seems to run in any particular order. 900 # Here at least we want to group them together by class. 901 rmap = {} 902 classes = [] 903 for n, t, o, e in result_list: 904 cls = t.__class__ 905 if not cls in rmap: 906 rmap[cls] = [] 907 classes.append(cls) 908 rmap[cls].append((n, t, o, e)) 909 r = [(cls, rmap[cls]) for cls in classes] 910 return r 911 912 def getReportAttributes(self, result): 913 """ 914 Return report attributes as a list of (name, value). 915 Override this to add custom attributes. 916 """ 917 startTime = str(self.startTime)[:19] 918 duration = str(self.stopTime - self.startTime) 919 status = [] 920 if result.success_count: 921 status.append(u'Pass %s' % result.success_count) 922 if result.failure_count: 923 status.append(u'Failure %s' % result.failure_count) 924 if result.error_count: 925 status.append(u'Error %s' % result.error_count) 926 if status: 927 status = ' '.join(status) 928 else: 929 status = 'none' 930 return [ 931 (u'开始时间', startTime), 932 (u'耗时', duration), 933 (u'状态', status), 934 ] 935 936 def generateReport(self, test, result): 937 report_attrs = self.getReportAttributes(result) 938 generator = 'HTMLTestRunner %s' % __version__ 939 stylesheet = self._generate_stylesheet() 940 heading = self._generate_heading(report_attrs) 941 report = self._generate_report(result) 942 ending = self._generate_ending() 943 output = self.HTML_TMPL % dict( 944 title=saxutils.escape(self.title), 945 generator=generator, 946 stylesheet=stylesheet, 947 heading=heading, 948 report=report, 949 ending=ending, 950 ) 951 if PY3K: 952 self.stream.write(output.encode()) 953 else: 954 self.stream.write(output.encode('utf8')) 955 956 def _generate_stylesheet(self): 957 return self.STYLESHEET_TMPL 958 959 def _generate_heading(self, report_attrs): 960 a_lines = [] 961 for name, value in report_attrs: 962 line = self.HEADING_ATTRIBUTE_TMPL % dict( 963 name=name, 964 value=value, 965 ) 966 a_lines.append(line) 967 heading = self.HEADING_TMPL % dict( 968 title=saxutils.escape(self.title), 969 parameters=''.join(a_lines), 970 description=saxutils.escape(self.description), 971 ) 972 return heading 973 974 def _generate_report(self, result): 975 rows = [] 976 sortedResult = self.sortResult(result.result) 977 for cid, (cls, cls_results) in enumerate(sortedResult): 978 # subtotal for a class 979 np = nf = ne = 0 980 for n, t, o, e in cls_results: 981 if n == 0: 982 np += 1 983 elif n == 1: 984 nf += 1 985 else: 986 ne += 1 987 988 # format class description 989 if cls.__module__ == "__main__": 990 name = cls.__name__ 991 else: 992 name = "%s.%s" % (cls.__module__, cls.__name__) 993 doc = cls.__doc__ and cls.__doc__.split("\n")[0] or "" 994 desc = doc and '%s: %s' % (name, doc) or name 995 if not PY3K: 996 if isinstance(desc, str): 997 desc = desc.decode("utf-8") 998 999 row = self.REPORT_CLASS_TMPL % dict( 1000 style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', 1001 desc=desc, 1002 count=np + nf + ne, 1003 Pass=np, 1004 fail=nf, 1005 error=ne, 1006 cid='c%s' % (cid + 1), 1007 ) 1008 rows.append(row) 1009 1010 for tid, (n, t, o, e) in enumerate(cls_results): 1011 self._generate_report_test(rows, cid, tid, n, t, o, e) 1012 1013 report = self.REPORT_TMPL % dict( 1014 test_list=u''.join(rows), 1015 count=str(result.success_count + result.failure_count + result.error_count), 1016 Pass=str(result.success_count), 1017 fail=str(result.failure_count), 1018 error=str(result.error_count), 1019 ) 1020 return report 1021 1022 def _generate_report_test(self, rows, cid, tid, n, t, o, e): 1023 # e.g. 'pt1.1', 'ft1.1', etc 1024 has_output = bool(o or e) 1025 tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid + 1, tid + 1) 1026 name = t.id().split('.')[-1] 1027 if self.verbosity > 1: 1028 doc = t._testMethodDoc or '' 1029 else: 1030 doc = "" 1031 1032 desc = doc and ('%s: %s' % (name, doc)) or name 1033 if not PY3K: 1034 if isinstance(desc, str): 1035 desc = desc.decode("utf-8") 1036 tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL 1037 1038 # o and e should be byte string because they are collected from stdout and stderr? 1039 if isinstance(o, str): 1040 # uo = unicode(o.encode('string_escape')) 1041 if PY3K: 1042 uo = o 1043 else: 1044 uo = o.decode('utf-8', 'ignore') 1045 else: 1046 uo = o 1047 if isinstance(e, str): 1048 # ue = unicode(e.encode('string_escape')) 1049 if PY3K: 1050 ue = e 1051 elif e.find("Error") != -1 or e.find("Exception") != -1: 1052 es = e.decode('utf-8', 'ignore').split('\n') 1053 es[-2] = es[-2].decode('unicode_escape') 1054 ue = u"\n".join(es) 1055 else: 1056 ue = e.decode('utf-8', 'ignore') 1057 else: 1058 ue = e 1059 1060 script = self.REPORT_TEST_OUTPUT_TMPL % dict( 1061 id=tid, 1062 output=saxutils.escape(uo + ue), 1063 ) 1064 if getattr(t,'imgs',[]): 1065 # 判断截图列表,如果有则追加 1066 tmp = u"" 1067 for i, img in enumerate(t.imgs): 1068 if i==0: 1069 tmp+=""" \n""" % img 1070 else: 1071 tmp+=""" \n""" % img 1072 imgs = self.IMG_TMPL % dict(imgs=tmp) 1073 else: 1074 imgs = u"""无截图""" 1075 1076 row = tmpl % dict( 1077 tid=tid, 1078 Class=(n == 0 and 'hiddenRow' or 'none'), 1079 style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'), 1080 desc=desc, 1081 script=script, 1082 status=self.STATUS[n], 1083 img=imgs, 1084 ) 1085 rows.append(row) 1086 if not has_output: 1087 return 1088 1089 def _generate_ending(self): 1090 return self.ENDING_TMPL 1091 1092 1093 ############################################################################## 1094 # Facilities for running tests from the command line 1095 ############################################################################## 1096 1097 # Note: Reuse unittest.TestProgram to launch test. In the future we may 1098 # build our own launcher to support more specific command line 1099 # parameters like test title, CSS, etc. 1100 class TestProgram(unittest.TestProgram): 1101 """ 1102 A variation of the unittest.TestProgram. Please refer to the base 1103 class for command line parameters. 1104 """ 1105 1106 def runTests(self): 1107 # Pick HTMLTestRunner as the default test runner. 1108 # base class's testRunner parameter is not useful because it means 1109 # we have to instantiate HTMLTestRunner before we know self.verbosity. 1110 if self.testRunner is None: 1111 self.testRunner = HTMLTestRunner(verbosity=self.verbosity) 1112 unittest.TestProgram.runTests(self) 1113 1114 1115 main = TestProgram 1116 1117 ############################################################################## 1118 # Executing this module from the command line 1119 ############################################################################## 1120 1121 if __name__ == "__main__": 1122 main(module=None)

 

转载于:https://www.cnblogs.com/jayson-0425/p/10037090.html

你可能感兴趣的:(HTMLTestRunner)