自动化测试框架 一
Posted testyuz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自动化测试框架 一相关的知识,希望对你有一定的参考价值。
1 # 封装配置文件的代码 2 3 from configparser import ConfigParser 4 5 6 class HandleConfig(ConfigParser): 7 """ 8 定义处理配置文件的类 9 """ 10 11 def __init__(self): # 对父类的构造方法进行拓展 12 # 调用父类的构造方法 13 super().__init__() # 重写或者拓展父类的构造方法,往往我们需要先调用父类的构造方法 14 self.filename = ‘test_case.conf‘ 15 self.read(self.filename, encoding=‘utf-8‘) # 读取配置文件 16 # self 是 ConfigParer 的对象,因为 HandleConfig 继承了父类,所以可以直接调用 17 18 def __call__(self, section=‘DEFAULT‘, option=None, is_eval=False, is_bool=False): 19 """ 20 ’对象()‘这种形式,__call__ 方法会被调用 21 :param section: 区域名 22 :param option: 选项名 23 :param is_eval: 为默认参数,是否进行 eval 函数转换,默认不转换 24 :param is_bool: 选项所对应的值是否需要转换为 bool 类型,默认不转换 25 :return: 26 """ 27 if option is None: 28 # 1、一个对象() --> 返回 DEFAULT 默认区域下的所有选项,构造成一个字典 29 # 2、一个对象(区域名) --> 能够获取此区域下的所有选项,返回一个字典 30 return dict(self[section]) 31 32 if isinstance(is_bool, bool): 33 if is_bool: 34 # 3、一个对象(区域名, 选项名,is_bool=True) --> 将获取到的数据使用 getboolean() 方法来获取 35 return self.getboolean(section, option) 36 else: 37 raise ValueError(‘is_bool 必须是 bool 类型。‘) # 手动抛出异常 38 39 data = self.get(section, option) 40 # 4、一个对象(区域名, 选项名) --> 通过区域名以及选项名来获取值 41 42 # 5、如果获取到的数据为数字类型的字符串,自动转换为 python 中的 int 类型 43 if data.isdigit(): # 判断是否为数字类型的字符串 44 return int(data) 45 try: 46 return float(data) # 如果为浮点类型的字符串,则直接转换为 float 类型 47 except ValueError: 48 pass 49 50 if isinstance(is_eval, bool): 51 if is_eval: 52 # 6、一个对象(is_eval=True) --> 将获取到的数据使用 eval 函数进行转换 53 return eval(data) 54 else: 55 raise ValueError(‘is_eval 必须是 bool 类型。‘) 56 57 return data 58 59 60 do_config = HandleConfig() 61 62 63 if __name__ == ‘__main__‘: 64 config = HandleConfig() 65 print(config()) 66 print(config(‘excel‘)) 67 a = config(‘excel‘, ‘two_res‘) # print(config(‘excel‘, ‘actual_col‘)) 68 print(f‘值为:{a} 类型为:{type(a)}‘) 69 b = config(‘excel‘, ‘two_res‘, is_bool=True) # print(config(‘excel‘, ‘two_res‘, is_bool=True)) 70 print(f‘值为:{b} 类型为:{type(b)}‘) 71 c = config(‘excel‘, ‘five_res‘, is_eval=True) 72 print(f‘值为:{c} 类型为:{type(c)}‘) # print(config(‘excel‘, ‘five_res‘, is_eval=True))
1 # excel 封装成类 2 from openpyxl import load_workbook 3 from collections import namedtuple 4 from class_13_0111_rewrite_unittest.config_handle import do_config 5 6 7 class HandleExcel(object): 8 """ 9 定义处理 excel 的类 10 """ 11 # config = HandleConfig() # 定义一个类属性,创建 HandleConfig() 对象 12 13 def __init__(self, filename, sheetname=None): 14 self.filename = filename 15 self.sheetname = sheetname 16 # 定义一个空列表,存放所有 cases 命名元组对象 17 self.case_list = [] 18 # 打开 excel 文件 19 self.wb = load_workbook(self.filename) 20 # 定位表单 21 if self.sheetname is None: # 如果没有传 sheetname 这个参数,就默认获取第一个表单 22 self.ws = self.wb.active 23 else: 24 self.ws = self.wb[self.sheetname] 25 # 取 excel 表头,构造出来一个元组 26 self.sheet_head_tuple = tuple(self.ws.iter_rows(max_row=1, values_only=True))[0] 27 # 创建一个元组类 28 self.cases = namedtuple("cases", self.sheet_head_tuple, rename=True) 29 # 三元运算 30 # self.ws = self.wb[self.sheetname] if self.sheetname is not None else self.wb.active 31 32 def get_cases(self): 33 """ 34 获取所有的测试用例 35 :return: 存放 cases 命名元组的列表 36 """ 37 for row_data in self.ws.iter_rows(min_row=2, values_only=True): 38 self.case_list.append(self.cases(*row_data)) 39 return self.case_list 40 41 def get_case(self, row): 42 """ 43 获取某一条测试用例 44 :param row: 行号 45 :return: 一个 cases 命名元组对象 46 """ 47 # 判断行号是否符合条件,(数字,和 行号早最大行和最小行之间) 48 if isinstance(row, int) and (2 <= row <= self.ws.max_row): 49 return tuple(self.ws.iter_rows(min_row=row, max_row=row, values_only=True))[0] 50 # return self.case_list[row] # 这种方法不可取是因为,如果不调用 get_cases ,self.cases_list 就为空 51 else: 52 print("传入行号参数有误!") 53 54 def write_result(self, row, actual, result): 55 """ 56 将实际值与测试用例执行的结果写入 excel 57 :param row: 行号 58 :param actual: 实际值 59 :param result: 测试结果: “Pass” or “Fail” 60 :return: 61 """ 62 other_wb = load_workbook(self.filename) 63 other_ws = other_wb[self.sheetname] 64 if isinstance(row, int) and (2 <= row <= self.ws.max_row): 65 other_ws.cell(row=row, column=do_config(‘excel‘, ‘actual_col‘), value=actual) 66 other_ws.cell(row=row, column=do_config(‘excel‘, ‘result_col‘), value=result) 67 other_wb.save(self.filename) 68 other_wb.close() 69 else: 70 print("传入行号参数有误!") 71 72 73 # do_excel = HandleExcel(do_config(‘file name‘, ‘cases_path‘)) 74 75 if __name__ == ‘__main__‘: 76 # file_name = "cases.xlsx" 77 # one_excel = HandleExcel(filename=file_name) 78 cases = do_excel.get_cases() 79 print(cases) 80 one_case = do_excel.get_case(2) 81 print(do_excel) 82 do_excel.write_result(2, "ces", "测试")
1 # 日志类的封装 2 import logging 3 from logging.handlers import RotatingFileHandler 4 from class_13_0111_rewrite_unittest.config_handle import do_config 5 6 7 class HandleLog: 8 """ 9 日志类的封装 10 """ 11 def __init__(self): 12 self.case_logger = logging.getLogger(do_config(‘log‘, ‘logger_name‘)) 13 self.case_logger.setLevel(do_config(‘log‘, ‘logger_level‘)) 14 console_handle = logging.StreamHandler() 15 file_handle = RotatingFileHandler(filename=do_config(‘log‘, ‘log_filename‘), 16 maxBytes=do_config(‘log‘, ‘maxBytes‘), 17 backupCount=do_config(‘log‘, ‘backupCount‘), 18 encoding=‘utf-8‘) 19 console_handle.setLevel(do_config(‘log‘, ‘console_level‘)) 20 file_handle.setLevel(do_config(‘log‘, ‘file_level‘)) 21 simple_formatter = logging.Formatter(do_config(‘log‘, ‘simple_formatter‘)) 22 verbose_formatter = logging.Formatter(do_config(‘log‘, ‘verbose_formatter‘)) 23 console_handle.setFormatter(simple_formatter) 24 file_handle.setFormatter(verbose_formatter) 25 self.case_logger.addHandler(console_handle) 26 self.case_logger.addHandler(file_handle) 27 28 def get_logger(self): 29 """ 30 获取 logger 日志器对象 31 :return: 32 """ 33 return self.case_logger 34 35 36 do_log = HandleLog().get_logger() 37 38 39 if __name__ == ‘__main__‘: 40 for _ in range(1000): 41 do_log.debug(‘这是 debug 级别的日志。‘) 42 do_log.info(‘这是 info 级别的日志。‘) 43 do_log.warning(‘这是 warning 级别的日志。‘) 44 do_log.error(‘这是 error 级别的日志。‘) 45 do_log.critical(‘这是 critical 级别的日志。‘)
1 # 文件路径配置 2 [file name] 3 # cases_path 为测试用例 excel 文件的路径 4 # log_path 为记录日志的文件路径 5 cases_path = cases.xlsx 6 log_path = run_result_1.txt 7 report_html_name = test_result 8 9 # 提示信息 10 [msg] 11 ; success_result 为用例执行成功的提示信息 12 ; fail_path 为用例执行失败的提示信息 13 success_result = Pass 14 fail_result = Fail 15 16 # excel 相关配置 17 [excel] 18 # actual_col 为将用例执行的实际结果存储到 excel 中的列号 19 # result_col 为将用例执行的结果存储到 excel 中的列号 20 actual_col = 6 21 result_col = 7 22 23 # 日志相关配置 24 [log] 25 # 日志器名称 26 logger_name = case 27 # 日志器日志等级 28 logger_level = DEBUG 29 # 日志文件名 30 log_filename = case.log 31 # 一个日志文件的最大字节数 32 maxBytes = 4096 33 # 备份文件数 34 backupCount = 3 35 # 输出到控制台的日志等级 36 console_level = ERROR 37 # 输出到文件的日志等级 38 file_level = INFO 39 # 控制台使用的日志简单格式 40 # 配置文件出现 % ,需要使用 % 来转义 41 simple_formatter = %%(asctime)s - [%%(levelname)s] - [日志信息]:%%(message)s 42 # 日志文件使用日志复杂格式 43 verbose_formatter = %%(asctime)s - [%%(levelname)s] - %%(module)s - %%(name)s - %%(lineno)d - [日志信息]:%%(message)s
1 # 美化报告的第三方库 2 # coding=utf-8 3 """ 4 A连接信息 TestRunner for use with the Python unit testing framework. It 5 generates a HTML report to show the result at a glance. 6 The simplest way to use this is to invoke its main method. E列表.g. 7 import unittest 8 import HTMLTestRunner 9 ... define your tests ... 10 if __name__ == ‘__main__‘: 11 HTMLTestRunner.main() 12 For more customization options, instantiates a HTMLTestRunner object. 13 HTMLTestRunner is a counterpart to unittest‘s TextTestRunner. E列表.g. 14 # output to a file 15 fp = file(‘my_report.html‘, ‘wb‘) 16 runner = HTMLTestRunner.HTMLTestRunner( 17 stream=fp, 18 title=‘My unit test‘, 19 description=‘This demonstrates the report output by HTMLTestRunner.‘ 20 ) 21 # Use an external stylesheet. 22 # See the Template_mixin class for more customizable options 23 runner.STYLESHEET_TMPL = ‘<link rel="stylesheet" href="my_stylesheet.css" type="text/css">‘ 24 # run the test 25 runner.run(my_test_suite) 26 ------------------------------------------------------------------------ 27 Copyright (c) 2004-2007, Wai Yip Tung 28 All rights reserved. 29 Redistribution and use in source and binary forms, with or without 30 modification, are permitted provided that the following conditions are 31 met: 32 * Redistributions of source code must retain the above copyright notice, 33 this list of conditions and the following disclaimer. 34 * Redistributions in binary form must reproduce the above copyright 35 notice, this list of conditions and the following disclaimer in the 36 documentation and/or other materials provided with the distribution. 37 * Neither the name Wai Yip Tung nor the names of its contributors may be 38 used to endorse or promote products derived from this software without 39 specific prior written permission. 40 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 41 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 42 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A连接信息 43 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 44 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 45 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 46 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 47 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 48 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 49 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 50 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 51 """ 52 53 # URL: http://tungwaiyip.info/software/HTMLTestRunner.html 54 55 __author__ = "Wai Yip Tung, Findyou" 56 __version__ = "0.8.2.2" 57 58 """ 59 Change History 60 Version 0.8.2.1 -Findyou 61 * 改为支持python3 62 Version 0.8.2.1 -Findyou 63 * 支持中文,汉化 64 * 调整样式,美化(需要连入网络,使用的百度的Bootstrap.js) 65 * 增加 通过分类显示、测试人员、通过率的展示 66 * 优化“详细”与“收起”状态的变换 67 * 增加返回顶部的锚点 68 Version 0.8.2 69 * Show output inline instead of popup window (Viorel Lupu). 70 Version in 0.8.1 71 * Validated XHTML (Wolfgang Borgert). 72 * Added description of test classes and test cases. 73 Version in 0.8.0 74 * Define Template_mixin class for customization. 75 * Workaround a IE 6 bug that it does not treat <script> block as CDATA. 76 Version in 0.7.1 77 * Back port to Python 2.3 (Frank Horowitz). 78 * Fix missing scroll bars in detail log (Podi). 79 """ 80 81 # TODO: color stderr 82 # TODO: simplify javascript using ,ore than 1 class in the class attribute? 83 84 import datetime 85 import io 86 import sys 87 import time 88 import unittest 89 from xml.sax import saxutils 90 91 92 # ------------------------------------------------------------------------ 93 # The redirectors below are used to capture output during testing. Output 94 # sent to sys.stdout and sys.stderr are automatically captured. However 95 # in some cases sys.stdout is already cached before HTMLTestRunner is 96 # invoked (e.g. calling logging.basicConfig). In order to capture those 97 # output, use the redirectors for the cached stream. 98 # 99 # e.g. 100 # >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector) 101 # >>> 102 103 class OutputRedirector(object): 104 """ Wrapper to redirect stdout or stderr """ 105 106 def __init__(self, fp): 107 self.fp = fp 108 109 def write(self, s): 110 self.fp.write(s) 111 112 def writelines(self, lines): 113 self.fp.writelines(lines) 114 115 def flush(self): 116 self.fp.flush() 117 118 119 stdout_redirector = OutputRedirector(sys.stdout) 120 stderr_redirector = OutputRedirector(sys.stderr) 121 122 123 # ---------------------------------------------------------------------- 124 # Template 125 126 class Template_mixin(object): 127 """ 128 Define a HTML template for report customerization and generation. 129 Overall structure of an HTML report 130 HTML 131 +------------------------+ 132 |<html> | 133 | <head> | 134 | | 135 | STYLESHEET | 136 | +----------------+ | 137 | | | | 138 | +----------------+ | 139 | | 140 | </head> | 141 | | 142 | <body> | 143 | | 144 | HEADING | 145 | +----------------+ | 146 | | | | 147 | +----------------+ | 148 | | 149 | REPORT | 150 | +----------------+ | 151 | | | | 152 | +----------------+ | 153 | | 154 | ENDING | 155 | +----------------+ | 156 | | | | 157 | +----------------+ | 158 | | 159 | </body> | 160 |</html> | 161 +------------------------+ 162 """ 163 164 STATUS = { 165 0: ‘通过‘, 166 1: ‘失败‘, 167 2: ‘错误‘, 168 } 169 170 DEFAULT_TITLE = ‘2019年的自动化测试报告‘ 171 DEFAULT_DESCRIPTION = ‘‘ 172 DEFAULT_TESTER = ‘华华‘ 173 174 # ------------------------------------------------------------------------ 175 # HTML Template 176 177 HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?> 178 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 179 <html xmlns="http://www.w3.org/1999/xhtml"> 180 <head> 181 <title>%(title)s</title> 182 <meta name="generator" content="%(generator)s"/> 183 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 184 <link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> 185 <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> 186 <script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script> 187 %(stylesheet)s 188 </head> 189 <body > 190 <script language="javascript" type="text/javascript"> 191 output_list = Array(); 192 /*level 调整增加只显示通过用例的分类 --Findyou 193 0:Summary //all hiddenRow 194 1:Failed //pt hiddenRow, ft none 195 2:Pass //pt none, ft hiddenRow 196 3:All //pt none, ft none 197 */ 198 function showCase(level) { 199 trs = document.getElementsByTagName("tr"); 200 for (var i = 0; i < trs.length; i++) { 201 tr = trs[i]; 202 id = tr.id; 203 if (id.substr(0,2) == ‘ft‘) { 204 if (level == 2 || level == 0 ) { 205 tr.className = ‘hiddenRow‘; 206 } 207 else { 208 tr.className = ‘‘; 209 } 210 } 211 if (id.substr(0,2) == ‘pt‘) { 212 if (level < 2) { 213 tr.className = ‘hiddenRow‘; 214 } 215 else { 216 tr.className = ‘‘; 217 } 218 } 219 } 220 //加入【详细】切换文字变化 --Findyou 221 detail_class=document.getElementsByClassName(‘detail‘); 222 //console.log(detail_class.length) 223 if (level == 3) { 224 for (var i = 0; i < detail_class.length; i++){ 225 detail_class[i].innerHTML="收起" 226 } 227 } 228 else{ 229 for (var i = 0; i < detail_class.length; i++){ 230 detail_class[i].innerHTML="详细" 231 } 232 } 233 } 234 function showClassDetail(cid, count) { 235 var id_list = Array(count); 236 var toHide = 1; 237 for (var i = 0; i < count; i++) { 238 //ID修改 点 为 下划线 -Findyou 239 tid0 = ‘t‘ + cid.substr(1) + ‘_‘ + (i+1); 240 tid = ‘f‘ + tid0; 241 tr = document.getElementById(tid); 242 if (!tr) { 243 tid = ‘p‘ + tid0; 244 tr = document.getElementById(tid); 245 } 246 id_list[i] = tid; 247 if (tr.className) { 248 toHide = 0; 249 } 250 } 251 for (var i = 0; i < count; i++) { 252 tid = id_list[i]; 253 //修改点击无法收起的BUG,加入【详细】切换文字变化 --Findyou 254 if (toHide) { 255 document.getElementById(tid).className = ‘hiddenRow‘; 256 document.getElementById(cid).innerText = "详细" 257 } 258 else { 259 document.getElementById(tid).className = ‘‘; 260 document.getElementById(cid).innerText = "收起" 261 } 262 } 263 } 264 function html_escape(s) { 265 s = s.replace(/&/g,‘&‘); 266 s = s.replace(/</g,‘<‘); 267 s = s.replace(/>/g,‘>‘); 268 return s; 269 } 270 </script> 271 %(heading)s 272 %(report)s 273 %(ending)s 274 </body> 275 </html> 276 """ 277 # variables: (title, generator, stylesheet, heading, report, ending) 278 279 # ------------------------------------------------------------------------ 280 # Stylesheet 281 # 282 # alternatively use a <link> for external style sheet, e.g. 283 # <link rel="stylesheet" href="$url" type="text/css"> 284 285 STYLESHEET_TMPL = """ 286 <style type="text/css" media="screen"> 287 body { font-family: Microsoft YaHei,Tahoma,arial,helvetica,sans-serif;padding: 20px; font-size: 120%; } 288 table { font-size: 100%; } 289 /* -- heading ---------------------------------------------------------------------- */ 290 .heading { 291 margin-top: 0ex; 292 margin-bottom: 1ex; 293 } 294 .heading .description { 295 margin-top: 4ex; 296 margin-bottom: 6ex; 297 } 298 /* -- report ------------------------------------------------------------------------ */ 299 #total_row { font-weight: bold; } 300 .passCase { color: #5cb85c; } 301 .failCase { color: #d9534f; font-weight: bold; } 302 .errorCase { color: #f0ad4e; font-weight: bold; } 303 .hiddenRow { display: none; } 304 .testcase { margin-left: 2em; } 305 </style> 306 """ 307 308 # ------------------------------------------------------------------------ 309 # Heading 310 # 311 312 HEADING_TMPL = """<div class=‘heading‘> 313 <h1 style="font-family: Microsoft YaHei">%(title)s</h1> 314 %(parameters)s 315 <p class=‘description‘>%(description)s</p> 316 </div> 317 """ # variables: (title, parameters, description) 318 319 HEADING_ATTRIBUTE_TMPL = """<p class=‘attribute‘><strong>%(name)s : </strong> %(value)s</p> 320 """ # variables: (name, value) 321 322 # ------------------------------------------------------------------------ 323 # Report 324 # 325 # 汉化,加美化效果 --Findyou 326 REPORT_TMPL = """ 327 <p id=‘show_detail_line‘> 328 <a class="btn btn-primary" href=‘javascript:showCase(0)‘>概要{ %(passrate)s }</a> 329 <a class="btn btn-danger" href=‘javascript:showCase(1)‘>失败{ %(fail)s }</a> 330 <a class="btn btn-success" href=‘javascript:showCase(2)‘>通过{ %(Pass)s }</a> 331 <a class="btn btn-info" href=‘javascript:showCase(3)‘>所有{ %(count)s }</a> 332 </p> 333 <table id=‘result_table‘ class="table table-condensed table-bordered table-hover"> 334 <colgroup> 335 <col align=‘left‘ /> 336 <col align=‘right‘ /> 337 <col align=‘right‘ /> 338 <col align=‘right‘ /> 339 <col align=‘right‘ /> 340 <col align=‘right‘ /> 341 </colgroup> 342 <tr id=‘header_row‘ class="text-center success" style="font-weight: bold;font-size: 16px;"> 343 <td>用例集/测试用例</td> 344 <td>总计</td> 345 <td>通过</td> 346 <td>失败</td> 347 <td>错误</td> 348 <td>详细</td> 349 </tr> 350 %(test_list)s 351 <tr id=‘total_row‘ class="text-center active"> 352 <td>总计</td> 353 <td>%(count)s</td> 354 <td>%(Pass)s</td> 355 <td>%(fail)s</td> 356 <td>%(error)s</td> 357 <td>通过率:%(passrate)s</td> 358 </tr> 359 </table> 360 """ # variables: (test_list, count, Pass, fail, error ,passrate) 361 362 REPORT_CLASS_TMPL = r""" 363 <tr class=‘%(style)s warning‘> 364 <td>%(desc)s</td> 365 <td class="text-center">%(count)s</td> 366 <td class="text-center">%(Pass)s</td> 367 <td class="text-center">%(fail)s</td> 368 <td class="text-center">%(error)s</td> 369 <td class="text-center"><a href="javascript:showClassDetail(‘%(cid)s‘,%(count)s)" class="detail" id=‘%(cid)s‘>详细</a></td> 370 </tr> 371 """ # variables: (style, desc, count, Pass, fail, error, cid) 372 373 # 失败 的样式,去掉原来JS效果,美化展示效果 -Findyou 374 REPORT_TEST_WITH_OUTPUT_TMPL = r""" 375 <tr id=‘%(tid)s‘ class=‘%(Class)s‘> 376 <td class=‘%(style)s‘><div class=‘testcase‘>%(desc)s</div></td> 377 <td colspan=‘5‘ align=‘center‘> 378 <!--默认收起错误信息 -Findyou 379 <button id=‘btn_%(tid)s‘ type="button" class="btn btn-danger btn-xs collapsed" data-toggle="collapse" data-target=‘#div_%(tid)s‘>%(status)s</button> 380 <div id=‘div_%(tid)s‘ class="collapse"> --> 381 <!-- 默认展开错误信息 -Findyou --> 382 <button id=‘btn_%(tid)s‘ type="button" class="btn btn-danger btn-xs" data-toggle="collapse" data-target=‘#div_%(tid)s‘>%(status)s</button> 383 <div id=‘div_%(tid)s‘ class="collapse in" align="left"> 384 <pre> 385 %(script)s 386 </pre> 387 </div> 388 </td> 389 </tr> 390 """ # variables: (tid, Class, style, desc, status) 391 392 # 通过 的样式,加标签效果 -Findyou 393 REPORT_TEST_NO_OUTPUT_TMPL = r""" 394 <tr id=‘%(tid)s‘ class=‘%(Class)s‘> 395 <td class=‘%(style)s‘><div class=‘testcase‘>%(desc)s</div></td> 396 <td colspan=‘5‘ align=‘center‘><span class="label label-success success">%(status)s</span></td> 397 </tr> 398 """ # variables: (tid, Class, style, desc, status) 399 400 REPORT_TEST_OUTPUT_TMPL = r""" 401 %(id)s: %(output)s 402 """ # variables: (id, output) 403 404 # ------------------------------------------------------------------------ 405 # ENDING 406 # 407 # 增加返回顶部按钮 --Findyou 408 ENDING_TMPL = """<div id=‘ending‘> </div> 409 <div style=" position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer"> 410 <a href="#"><span class="glyphicon glyphicon-eject" style = "font-size:30px;" aria-hidden="true"> 411 </span></a></div> 412 """ 413 414 415 # -------------------- The end of the Template class ------------------- 416 417 418 TestResult = unittest.TestResult 419 420 421 class _TestResult(TestResult): 422 # note: _TestResult is a pure representation of results. 423 # It lacks the output and reporting ability compares to unittest._TextTestResult. 424 425 def __init__(self, verbosity=1): 426 TestResult.__init__(self) 427 self.stdout0 = None 428 self.stderr0 = None 429 self.success_count = 0 430 self.failure_count = 0 431 self.error_count = 0 432 self.verbosity = verbosity 433 434 # result is a list of result in 4 tuple 435 # ( 436 # result code (0: success; 1: fail; 2: error), 437 # TestCase object, 438 # Test output (byte string), 439 # stack trace, 440 # ) 441 self.result = [] 442 # 增加一个测试通过率 --Findyou 443 self.passrate = float(0) 444 445 def startTest(self, test): 446 print("{0} - Start Test:{1}".format(time.asctime(), str(test))) 447 TestResult.startTest(self, test) 448 # just one buffer for both stdout and stderr 449 self.outputBuffer = io.StringIO() 450 stdout_redirector.fp = self.outputBuffer 451 stderr_redirector.fp = self.outputBuffer 452 self.stdout0 = sys.stdout 453 self.stderr0 = sys.stderr 454 sys.stdout = stdout_redirector 455 sys.stderr = stderr_redirector 456 457 def complete_output(self): 458 """ 459 Disconnect output redirection and return buffer. 460 Safe to call multiple times. 461 """ 462 if self.stdout0: 463 sys.stdout = self.stdout0 464 sys.stderr = self.stderr0 465 self.stdout0 = None 466 self.stderr0 = None 467 return self.outputBuffer.getvalue() 468 469 def stopTest(self, test): 470 # Usually one of addSuccess, addError or addFailure would have been called. 471 # But there are some path in unittest that would bypass this. 472 # We must disconnect stdout in stopTest(), which is guaranteed to be called. 473 self.complete_output() 474 475 def addSuccess(self, test): 476 self.success_count += 1 477 TestResult.addSuccess(self, test) 478 output = self.complete_output() 479 self.result.append((0, test, output, ‘‘)) 480 if self.verbosity > 1: 481 sys.stderr.write(‘ok ‘) 482 sys.stderr.write(str(test)) 483 sys.stderr.write(‘ ‘) 484 else: 485 sys.stderr.write(‘.‘) 486 487 def addError(self, test, err): 488 self.error_count += 1 489 TestResult.addError(self, test, err) 490 _, _exc_str = self.errors[-1] 491 output = self.complete_output() 492 self.result.append((2, test, output, _exc_str)) 493 if self.verbosity > 1: 494 sys.stderr.write(‘E列表 ‘) 495 sys.stderr.write(str(test)) 496 sys.stderr.write(‘ ‘) 497 else: 498 sys.stderr.write(‘E列表‘) 499 500 def addFailure(self, test, err): 501 self.failure_count += 1 502 TestResult.addFailure(self, test, err) 503 _, _exc_str = self.failures[-1] 504 output = self.complete_output() 505 self.result.append((1, test, output, _exc_str)) 506 if self.verbosity > 1: 507 sys.stderr.write(‘F ‘) 508 sys.stderr.write(str(test)) 509 sys.stderr.write(‘ ‘) 510 else: 511 sys.stderr.write(‘F‘) 512 513 514 class HTMLTestRunner(Template_mixin): 515 """ 516 """ 517 518 def __init__(self, stream=sys.stdout, verbosity=2, title=None, description=None, tester=None): 519 self.stream = stream 520 self.verbosity = verbosity 521 if title is None: 522 self.title = self.DEFAULT_TITLE 523 else: 524 self.title = title 525 if description is None: 526 self.description = self.DEFAULT_DESCRIPTION 527 else: 528 self.description = description 529 if tester is None: 530 self.tester = self.DEFAULT_TESTER 531 else: 532 self.tester = tester 533 534 self.startTime = datetime.datetime.now() 535 536 def run(self, test): 537 "Run the given test case or test suite." 538 result = _TestResult(self.verbosity) 539 test(result) 540 self.stopTime = datetime.datetime.now() 541 self.generateReport(test, result) 542 print(‘ Time Elapsed: %s‘ % (self.stopTime - self.startTime), file=sys.stderr) 543 return result 544 545 def sortResult(self, result_list): 546 # unittest does not seems to run in any particular order. 547 # Here at least we want to group them together by class. 548 rmap = {} 549 classes = [] 550 for n, t, o, e in result_list: 551 cls = t.__class__ 552 if cls not in rmap: 553 rmap[cls] = [] 554 classes.append(cls) 555 rmap[cls].append((n, t, o, e)) 556 r = [(cls, rmap[cls]) for cls in classes] 557 return r 558 559 # 替换测试结果status为通过率 --Findyou 560 def getReportAttributes(self, result): 561 """ 562 Return report attributes as a list of (name, value). 563 Override this to add custom attributes. 564 """ 565 startTime = str(self.startTime)[:19] 566 duration = str(self.stopTime - self.startTime) 567 status = [] 568 status.append(‘共 %s‘ % (result.success_count + result.failure_count + result.error_count)) 569 if result.success_count: status.append(‘通过 %s‘ % result.success_count) 570 if result.failure_count: status.append(‘失败 %s‘ % result.failure_count) 571 if result.error_count: status.append(‘错误 %s‘ % result.error_count) 572 if status: 573 status = ‘,‘.join(status) 574 self.passrate = str("%.2f%%" % (float(result.success_count) / float( 575 result.success_count + result.failure_count + result.error_count) * 100)) 576 else: 577 status = ‘none‘ 578 return [ 579 (‘测试人员‘, self.tester), 580 (‘开始时间‘, startTime), 581 (‘合计耗时‘, duration), 582 (‘测试结果‘, status + ",通过率= " + self.passrate), 583 ] 584 585 def generateReport(self, test, result): 586 report_attrs = self.getReportAttributes(result) 587 generator = ‘HTMLTestRunner %s‘ % __version__ 588 stylesheet = self._generate_stylesheet() 589 heading = self._generate_heading(report_attrs) 590 report = self._generate_report(result) 591 ending = self._generate_ending() 592 output = self.HTML_TMPL % dict( 593 title=saxutils.escape(self.title), 594 generator=generator, 595 stylesheet=stylesheet, 596 heading=heading, 597 report=report, 598 ending=ending, 599 ) 600 self.stream.write(output.encode(‘utf8‘)) 601 602 def _generate_stylesheet(self): 603 return self.STYLESHEET_TMPL 604 605 # 增加Tester显示 -Findyou 606 def _generate_heading(self, report_attrs): 607 a_lines = [] 608 for name, value in report_attrs: 609 line = self.HEADING_ATTRIBUTE_TMPL % dict( 610 name=saxutils.escape(name), 611 value=saxutils.escape(value), 612 ) 613 a_lines.append(line) 614 heading = self.HEADING_TMPL % dict( 615 title=saxutils.escape(self.title), 616 parameters=‘‘.join(a_lines), 617 description=saxutils.escape(self.description), 618 tester=saxutils.escape(self.tester), 619 ) 620 return heading 621 622 # 生成报告 --Findyou添加注释 623 def _generate_report(self, result): 624 rows = [] 625 sortedResult = self.sortResult(result.result) 626 for cid, (cls, cls_results) in enumerate(sortedResult): 627 # subtotal for a class 628 np = nf = ne = 0 629 for n, t, o, e in cls_results: 630 if n == 0: 631 np += 1 632 elif n == 1: 633 nf += 1 634 else: 635 ne += 1 636 637 # format class description 638 if cls.__module__ == "__main__": 639 name = cls.__name__ 640 else: 641 name = "%s.%s" % (cls.__module__, cls.__name__) 642 doc = cls.__doc__ and cls.__doc__.split(" ")[0] or "" 643 desc = doc and ‘%s: %s‘ % (name, doc) or name 644 645 row = self.REPORT_CLASS_TMPL % dict( 646 style=ne > 0 and ‘errorClass‘ or nf > 0 and ‘failClass‘ or ‘passClass‘, 647 desc=desc, 648 count=np + nf + ne, 649 Pass=np, 650 fail=nf, 651 error=ne, 652 cid=‘c%s‘ % (cid + 1), 653 ) 654 rows.append(row) 655 656 for tid, (n, t, o, e) in enumerate(cls_results): 657 self._generate_report_test(rows, cid, tid, n, t, o, e) 658 659 report = self.REPORT_TMPL % dict( 660 test_list=‘‘.join(rows), 661 count=str(result.success_count + result.failure_count + result.error_count), 662 Pass=str(result.success_count), 663 fail=str(result.failure_count), 664 error=str(result.error_count), 665 passrate=self.passrate, 666 ) 667 return report 668 669 def _generate_report_test(self, rows, cid, tid, n, t, o, e): 670 # e.g. ‘pt1.1‘, ‘ft1.1‘, etc 671 has_output = bool(o or e) 672 # ID修改点为下划线,支持Bootstrap折叠展开特效 - Findyou 673 tid = (n == 0 and ‘p‘ or ‘f‘) + ‘t%s_%s‘ % (cid + 1, tid + 1) 674 name = t.id().split(‘.‘)[-1] 675 doc = t.shortDescription() or "" 676 desc = doc and (‘%s: %s‘ % (name, doc)) or name 677 tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL 678 679 # utf-8 支持中文 - Findyou 680 # o and e should be byte string because they are collected from stdout and stderr? 681 if isinstance(o, str): 682 # TODO: some problem with ‘string_escape‘: it escape and mess up formating 683 # uo = unicode(o.encode(‘string_escape‘)) 684 # uo = o.decode(‘latin-1‘) 685 uo = o 686 else: 687 uo = o 688 if isinstance(e, str): 689 # TODO: some problem with ‘string_escape‘: it escape and mess up formating 690 # ue = unicode(e.encode(‘string_escape‘)) 691 # ue = e.decode(‘latin-1‘) 692 ue = e 693 else: 694 ue = e 695 696 script = self.REPORT_TEST_OUTPUT_TMPL % dict( 697 id=tid, 698 output=saxutils.escape(uo + ue), 699 ) 700 701 row = tmpl % dict( 702 tid=tid, 703 Class=(n == 0 and ‘hiddenRow‘ or ‘none‘), 704 style=n == 2 and ‘errorCase‘ or (n == 1 and ‘failCase‘ or ‘passCase‘), 705 desc=desc, 706 script=script, 707 status=self.STATUS[n], 708 ) 709 rows.append(row) 710 if not has_output: 711 return 712 713 def _generate_ending(self): 714 return self.ENDING_TMPL 715 716 717 ############################################################################## 718 # Facilities for running tests from the command line 719 ############################################################################## 720 721 # Note: Reuse unittest.TestProgram to launch test. In the future we may 722 # build our own launcher to support more specific command line 723 # parameters like test title, CSS, etc. 724 class TestProgram(unittest.TestProgram): 725 """ 726 A连接信息 variation of the unittest.TestProgram. Please refer to the base 727 class for command line parameters. 728 """ 729 730 def runTests(self): 731 # Pick HTMLTestRunner as the default test runner. 732 # base class‘s testRunner parameter is not useful because it means 733 # we have to instantiate HTMLTestRunner before we know self.verbosity. 734 if self.testRunner is None: 735 self.testRunner = HTMLTestRunner(verbosity=self.verbosity) 736 unittest.TestProgram.runTests(self) 737 738 739 main = TestProgram 740 741 ############################################################################## 742 # Executing this module from the command line 743 ############################################################################## 744 745 if __name__ == "__main__": 746 main(module=None)
1 # -*- coding: utf-8 -*- 2 # This file is a part of DDT (https://github.com/txels/ddt) 3 # Copyright 2012-2015 Carles Barrobés and DDT contributors 4 # For the exact contribution history, see the git revision log. 5 # DDT is licensed under the MIT License, included in 6 # https://github.com/txels/ddt/blob/master/LICENSE.md 7 8 import inspect 9 import json 10 import os 11 import re 12 import codecs 13 from functools import wraps 14 15 try: 16 import yaml 17 except ImportError: # pragma: no cover 18 _have_yaml = False 19 else: 20 _have_yaml = True 21 22 __version__ = ‘1.2.1‘ 23 24 # These attributes will not conflict with any real python attribute 25 # They are added to the decorated test method and processed later 26 # by the `ddt` class decorator. 27 28 DATA_ATTR = ‘%values‘ # store the data the test must run with 29 FILE_ATTR = ‘%file_path‘ # store the path to JSON file 30 UNPACK_ATTR = ‘%unpack‘ # remember that we have to unpack values 31 index_len = 5 # default max length of case index 32 33 try: 34 trivial_types = (type(None), bool, int, float, basestring) 35 except NameError: 36 trivial_types = (type(None), bool, int, float, str) 37 38 39 def is_trivial(value): 40 if isinstance(value, trivial_types): 41 return True 42 elif isinstance(value, (list, tuple)): 43 return all(map(is_trivial, value)) 44 return False 45 46 47 def unpack(func): 48 """ 49 Method decorator to add unpack feature. 50 51 """ 52 setattr(func, UNPACK_ATTR, True) 53 return func 54 55 56 def data(*values): 57 """ 58 Method decorator to add to your test methods. 59 60 Should be added to methods of instances of ``unittest.TestCase``. 61 62 """ 63 global index_len 64 index_len = len(str(len(values))) 65 return idata(values) 66 67 68 def idata(iterable): 69 """ 70 Method decorator to add to your test methods. 71 72 Should be added to methods of instances of ``unittest.TestCase``. 73 74 """ 75 76 def wrapper(func): 77 setattr(func, DATA_ATTR, iterable) 78 return func 79 80 return wrapper 81 82 83 def file_data(value): 84 """ 85 Method decorator to add to your test methods. 86 87 Should be added to methods of instances of ``unittest.TestCase``. 88 89 ``value`` should be a path relative to the directory of the file 90 containing the decorated ``unittest.TestCase``. The file 91 should contain JSON encoded data, that can either be a list or a 92 dict. 93 94 In case of a list, each value in the list will correspond to one 95 test case, and the value will be concatenated to the test method 96 name. 97 98 In case of a dict, keys will be used as suffixes to the name of the 99 test case, and values will be fed as test data. 100 101 """ 102 103 def wrapper(func): 104 setattr(func, FILE_ATTR, value) 105 return func 106 107 return wrapper 108 109 110 def mk_test_name(name, value, index=0): 111 """ 112 Generate a new name for a test case. 113 114 It will take the original test name and append an ordinal index and a 115 string representation of the value, and convert the result into a valid 116 python identifier by replacing extraneous characters with ``_``. 117 118 We avoid doing str(value) if dealing with non-trivial values. 119 The problem is possible different names with different runs, e.g. 120 different order of dictionary keys (see PYTHONHASHSEED) or dealing 121 with mock objects. 122 Trivial scalar values are passed as is. 123 124 A "trivial" value is a plain scalar, or a tuple or list consisting 125 only of trivial values. 126 """ 127 128 # Add zeros before index to keep order 129 index = "{0:0{1}}".format(index + 1, index_len) 130 131 # if not is_trivial(value): 132 # return "{0}_{1}".format(name, index) 133 134 # 添加对字典数据的处理 135 if not is_trivial(value) and not isinstance(value, (dict, tuple)): 136 return "{0}_{1}".format(name, index) 137 138 # 如果数据是字典,则获取字典当中的title对应的值,加到测试用例名称中 139 if isinstance(value, dict): 140 try: 141 value = value["title"] 142 except KeyError: 143 return "{0}_{1}".format(name, index) 144 145 # 如果数据是命名元组,则获取命名元组当中的title属性对应的值,加到测试用例名称中 146 if isinstance(value, tuple): 147 try: 148 value = value.title 149 except: 150 return "{0}_{1}".format(name, index) 151 152 try: 153 value = str(value) 154 except UnicodeEncodeError: 155 # fallback for python2 156 value = value.encode(‘ascii‘, ‘backslashreplace‘) 157 test_name = "{0}_{1}_{2}".format(name, index, value) 158 return re.sub(r‘W|^(?=d)‘, ‘_‘, test_name) 159 160 161 def feed_data(func, new_name, test_data_docstring, *args, **kwargs): 162 """ 163 This internal method decorator feeds the test data item to the test. 164 165 """ 166 167 @wraps(func) 168 def wrapper(self): 169 return func(self, *args, **kwargs) 170 171 wrapper.__name__ = new_name 172 wrapper.__wrapped__ = func 173 # set docstring if exists 174 if test_data_docstring is not None: 175 wrapper.__doc__ = test_data_docstring 176 else: 177 # Try to call format on the docstring 178 if func.__doc__: 179 try: 180 wrapper.__doc__ = func.__doc__.format(*args, **kwargs) 181 except (IndexError, KeyError): 182 # Maybe the user has added some of the formating strings 183 # unintentionally in the docstring. Do not raise an exception 184 # as it could be that user is not aware of the 185 # formating feature. 186 pass 187 return wrapper 188 189 190 def add_test(cls, test_name, test_docstring, func, *args, **kwargs): 191 """ 192 Add a test case to this class. 193 194 The test will be based on an existing function but will give it a new 195 name. 196 197 """ 198 setattr(cls, test_name, feed_data(func, test_name, test_docstring, 199 *args, **kwargs)) 200 201 202 def process_file_data(cls, name, func, file_attr): 203 """ 204 Process the parameter in the `file_data` decorator. 205 """ 206 cls_path = os.path.abspath(inspect.getsourcefile(cls)) 207 data_file_path = os.path.join(os.path.dirname(cls_path), file_attr) 208 209 def create_error_func(message): # pylint: disable-msg=W0613 210 def func(*args): 211 raise ValueError(message % file_attr) 212 213 return func 214 215 # If file does not exist, provide an error function instead 216 if not os.path.exists(data_file_path): 217 test_name = mk_test_name(name, "error") 218 test_docstring = """Error!""" 219 add_test(cls, test_name, test_docstring, 220 create_error_func("%s does not exist"), None) 221 return 222 223 _is_yaml_file = data_file_path.endswith((".yml", ".yaml")) 224 225 # Don‘t have YAML but want to use YAML file. 226 if _is_yaml_file and not _have_yaml: 227 test_name = mk_test_name(name, "error") 228 test_docstring = """Error!""" 229 add_test( 230 cls, 231 test_name, 232 test_docstring, 233 create_error_func("%s is a YAML file, please install PyYAML"), 234 None 235 ) 236 return 237 238 with codecs.open(data_file_path, ‘r‘, ‘utf-8‘) as f: 239 # Load the data from YAML or JSON 240 if _is_yaml_file: 241 data = yaml.safe_load(f) 242 else: 243 data = json.load(f) 244 245 _add_tests_from_data(cls, name, func, data) 246 247 248 def _add_tests_from_data(cls, name, func, data): 249 """ 250 Add tests from data loaded from the data file into the class 251 """ 252 for i, elem in enumerate(data): 253 if isinstance(data, dict): 254 key, value = elem, data[elem] 255 test_name = mk_test_name(name, key, i) 256 elif isinstance(data, list): 257 value = elem 258 test_name = mk_test_name(name, value, i) 259 if isinstance(value, dict): 260 add_test(cls, test_name, test_name, func, **value) 261 else: 262 add_test(cls, test_name, test_name, func, value) 263 264 265 def _is_primitive(obj): 266 """Finds out if the obj is a "primitive". It is somewhat hacky but it works. 267 """ 268 return not hasattr(obj, ‘__dict__‘) 269 270 271 def _get_test_data_docstring(func, value): 272 """Returns a docstring based on the following resolution strategy: 273 1. Passed value is not a "primitive" and has a docstring, then use it. 274 2. In all other cases return None, i.e the test name is used. 275 """ 276 if not _is_primitive(value) and value.__doc__: 277 return value.__doc__ 278 else: 279 return None 280 281 282 def ddt(cls): 283 """ 284 Class decorator for subclasses of ``unittest.TestCase``. 285 286 Apply this decorator to the test case class, and then 287 decorate test methods with ``@data``. 288 289 For each method decorated with ``@data``, this will effectively create as 290 many methods as data items are passed as parameters to ``@data``. 291 292 The names of the test methods follow the pattern 293 ``original_test_name_{ordinal}_{data}``. ``ordinal`` is the position of the 294 data argument, starting with 1. 295 296 For data we use a string representation of the data value converted into a 297 valid python identifier. If ``data.__name__`` exists, we use that instead. 298 299 For each method decorated with ``@file_data(‘test_data.json‘)``, the 300 decorator will try to load the test_data.json file located relative 301 to the python file containing the method that is decorated. It will, 302 for each ``test_name`` key create as many methods in the list of values 303 from the ``data`` key. 304 305 """ 306 for name, func in list(cls.__dict__.items()): 307 if hasattr(func, DATA_ATTR): 308 for i, v in enumerate(getattr(func, DATA_ATTR)): 309 test_name = mk_test_name(name, getattr(v, "__name__", v), i) 310 test_data_docstring = _get_test_data_docstring(func, v) 311 if hasattr(func, UNPACK_ATTR): 312 if isinstance(v, tuple) or isinstance(v, list): 313 add_test( 314 cls, 315 test_name, 316 test_data_docstring, 317 func, 318 *v 319 ) 320 else: 321 # unpack dictionary 322 add_test( 323 cls, 324 test_name, 325 test_data_docstring, 326 func, 327 **v 328 ) 329 else: 330 add_test(cls, test_name, test_data_docstring, func, v) 331 delattr(cls, name) 332 elif hasattr(func, FILE_ATTR): 333 file_attr = getattr(func, FILE_ATTR) 334 process_file_data(cls, name, func, file_attr) 335 delattr(cls, name) 336 return cls
执行文件、输出测试报告
1 import unittest 2 from datetime import datetime 3 4 5 # 导入第三方库模块 (导文件) 6 from class_13_0111_rewrite_unittest import HTMLTestRunnerNew 7 from class_13_0111_rewrite_unittest import test_multi as module1 8 from class_13_0111_rewrite_unittest import test_add as module2 9 from class_13_0111_rewrite_unittest.config_handle import do_config 10 11 # 创建加载器 12 one_loader = unittest.TestLoader() 13 test_module = (one_loader.loadTestsFromModule(module1), 14 one_loader.loadTestsFromModule(module2)) 15 16 # 创建测试套件 --> 报告名修改为 test_result + 年月日时间 17 one_suite = unittest.TestSuite(tests=test_module) 18 report_html_name = do_config(‘file name‘, ‘report_html_name‘) 19 report_html_name_full = report_html_name + ‘_‘ + datetime.strftime(datetime.now(), ‘%Y%m%d%H%M%S‘) + ‘.html‘ 20 21 # 创建运行器 22 with open(report_html_name_full, mode="wb") as save_to_file: 23 one_runner = HTMLTestRunnerNew.HTMLTestRunner(stream=save_to_file, 24 title="练习测试报告名称", 25 verbosity=2, 26 description="测试除法、乘法等用例的描述信息", 27 tester="测试人员名称") 28 one_runner.run(one_suite) 29 30 # stream 文件对象 31 # title 报告名称 32 # verbosity 报告详细程度 33 # description 描述信息 34 # tester 测试人员名称
测试类
1 class MathOperation: 2 """ 3 数学运算类 4 """ 5 def __init__(self, one_num, two_num): 6 self.one_num, self.two_num = one_num, two_num 7 8 def add(self): 9 """ 10 加法运算 11 :return: 两数相加之和 12 """ 13 return self.one_num + self.two_num 14 15 def minus(self): 16 """ 17 减法运算 18 :return: 两数相减之差 19 """ 20 return self.one_num - self.two_num 21 22 def mul(self): 23 """ 24 乘法运算 25 :return: 两数相乘之积 26 """ 27 return self.one_num * self.two_num 28 29 def divide(self): 30 """ 31 除法运算 32 :return: 两数相除之商 33 """ 34 try: 35 return round(self.one_num / self.two_num, 2) 36 except ZeroDivisionError: 37 return "∞" 38 39 40 if __name__ == ‘__main__‘: 41 nums = (10, 10) 42 math_operation = MathOperation(*nums) 43 # print("{} + {} = {}".format(*nums, math_operation.add())) 44 print(f"{math_operation.one_num} + {math_operation.two_num} = {math_operation.add()}") 45 print(f"{nums[0]} - {nums[1]} = {math_operation.minus()}") 46 print(f"{nums[0]} * {nums[1]} = {math_operation.mul()}") 47 print(f"{nums[0]} / {nums[1]} = {math_operation.divide()}")
测试module
1 import unittest 2 import inspect 3 4 from class_13_0111_rewrite_unittest.ddt_amend import ddt, data 5 from class_06_1209_unittest_02.math_operation import MathOperation 6 from openpyxl import load_workbook 7 from collections import namedtuple 8 9 from class_13_0111_rewrite_unittest.excel_handle import HandleExcel 10 from class_13_0111_rewrite_unittest.config_handle import do_config 11 from class_13_0111_rewrite_unittest.log_handle import do_log 12 13 14 do_excel = HandleExcel(do_config(‘file name‘, ‘cases_path‘), sheetname=‘Multi‘) 15 16 17 @ddt 18 class TestMulti(unittest.TestCase): 19 """ 20 测试两数相乘 21 """ 22 # handle_config = HandleConfig() 23 # handle_excel = HandleExcel(do_config(‘file name‘, ‘cases_path‘)) 24 cases = do_excel.get_cases() # 获取所有的用例,返回一个嵌套命名元组的列表 25 26 @classmethod 27 def setUpClass(cls) -> None: 28 """ 29 重写父类的类方法 30 所有用例执行之前,会被调用一次 --> 整个测试类调用之前,执行一次 31 :return: 32 """ 33 do_log.info("{:=^40s} ".format("开始执行用例!")) 34 35 @classmethod 36 def tearDownClass(cls) -> None: 37 """ 38 重写父类的类方法 39 所有用例执行之后,会被调用一次 --> 整个测试类调用之后,执行一次 40 :return: 41 """ 42 do_log.info("{:=^40s} ".format("用例执行结束!")) 43 44 @data(*cases) 45 def test_multi(self, data_namedtuple): 46 """ 47 两数相乘 48 :return: 49 """ 50 # 能够通过 inspect.stack 方法查看当前运行的实例名称 51 do_log.info(f"Running Test Method: {inspect.stack()[0][3]} ") 52 53 case_id = data_namedtuple.case_id 54 msg = data_namedtuple.title 55 l_date = data_namedtuple.l_date 56 r_date = data_namedtuple.r_date 57 expected = data_namedtuple.expected 58 59 # 获取实际结果 60 real_result = MathOperation(l_date, r_date).mul() 61 # 预期结果 62 expect_result = expected 63 # msg 64 run_success_msg = do_config(‘msg‘, ‘success_result‘) 65 run_fail_msg = do_config(‘msg‘, ‘fail_result‘) 66 # 断言 67 try: 68 self.assertEqual(expect_result, real_result, msg="{}失败!".format(msg)) 69 except AssertionError as e: 70 # 记录日志 71 do_log.error("{},执行结果为:{} 具体异常为:{} ".format(msg, run_fail_msg, e)) # 将执行失败的信息写入到文件 72 do_excel.write_result(row=case_id+1, actual=real_result, result=run_fail_msg) 73 raise e 74 else: 75 # self.file.write("{},执行结果为:{} ".format(msg, run_success_msg)) # 将执行成功的信息写入到文件 76 do_log.info("{},执行结果为:{} ".format(msg, run_success_msg)) 77 do_excel.write_result(row=case_id+1, actual=real_result, result=run_success_msg) 78 79 80 if __name__ == ‘__main__‘: 81 unittest.main()
1 import unittest 2 import inspect 3 4 from class_13_0111_rewrite_unittest.ddt_amend import ddt, data 5 from class_06_1209_unittest_02.math_operation import MathOperation 6 from openpyxl import load_workbook 7 from collections import namedtuple 8 9 from class_13_0111_rewrite_unittest.excel_handle import HandleExcel 10 from class_13_0111_rewrite_unittest.config_handle import do_config 11 from class_13_0111_rewrite_unittest.log_handle import do_log 12 13 14 do_excel = HandleExcel(do_config(‘file name‘, ‘cases_path‘), sheetname=‘Add‘) 15 16 17 @ddt 18 class TestMulti(unittest.TestCase): 19 """ 20 测试两数相乘 21 """ 22 # handle_config = HandleConfig() 23 # handle_excel = HandleExcel(do_config(‘file name‘, ‘cases_path‘)) 24 cases = do_excel.get_cases() # 获取所有的用例,返回一个嵌套命名元组的列表 25 26 @classmethod 27 def setUpClass(cls) -> None: 28 """ 29 重写父类的类方法 30 所有用例执行之前,会被调用一次 --> 整个测试类调用之前,执行一次 31 :return: 32 """ 33 do_log.info("{:=^40s} ".format("开始执行用例!")) 34 35 @classmethod 36 def tearDownClass(cls) -> None: 37 """ 38 重写父类的类方法 39 所有用例执行之后,会被调用一次 --> 整个测试类调用之后,执行一次 40 :return: 41 """ 42 do_log.info("{:=^40s} ".format("用例执行结束!")) 43 44 @data(*cases) 45 def test_multi(self, data_namedtuple): 46 """ 47 两数相乘 48 :return: 49 """ 50 # 能够通过 inspect.stack 方法查看当前运行的实例名称 51 do_log.info(f"Running Test Method: {inspect.stack()[0][3]} ") 52 53 case_id = data_namedtuple.case_id 54 msg = data_namedtuple.title 55 l_date = data_namedtuple.l_date 56 r_date = data_namedtuple.r_date 57 expected = data_namedtuple.expected 58 59 # 获取实际结果 60 real_result = MathOperation(l_date, r_date).add() 61 # 预期结果 62 expect_result = expected 63 # msg 64 run_success_msg = do_config(‘msg‘, ‘success_result‘) 65 run_fail_msg = do_config(‘msg‘, ‘fail_result‘) 66 # 断言 67 try: 68 self.assertEqual(expect_result, real_result, msg="{}失败!".format(msg)) 69 except AssertionError as e: 70 # 记录日志 71 do_log.error("{},执行结果为:{} 具体异常为:{} ".format(msg, run_fail_msg, e)) # 将执行失败的信息写入到文件 72 do_excel.write_result(row=case_id+1, actual=real_result, result=run_fail_msg) 73 raise e 74 else: 75 # self.file.write("{},执行结果为:{} ".format(msg, run_success_msg)) # 将执行成功的信息写入到文件 76 do_log.info("{},执行结果为:{} ".format(msg, run_success_msg)) 77 do_excel.write_result(row=case_id+1, actual=real_result, result=run_success_msg) 78 79 80 if __name__ == ‘__main__‘: 81 unittest.main()
excel 文件
~~待添加~~
以上是关于自动化测试框架 一的主要内容,如果未能解决你的问题,请参考以下文章