自动化测试框架 一

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", "测试")
处理 excel 的类
技术图片
 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,‘&amp;‘);
266     s = s.replace(/</g,‘&lt;‘);
267     s = s.replace(/>/g,‘&gt;‘);
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‘>&nbsp;</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)
HTMLTestRunnerNew.py
技术图片
  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(rW|^(?=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
重写后的 ddt 文件

执行文件、输出测试报告

技术图片
 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 测试人员名称
run_test.py

测试类

技术图片
 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()
测试 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=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()
测试 module 二

excel 文件

~~待添加~~ 

以上是关于自动化测试框架 一的主要内容,如果未能解决你的问题,请参考以下文章

干货 | 一文搞定 pytest 自动化测试实战

性能测试利器-Locust框架解析

接口测试 — 接口自动化框架的发送邮件实现

Flask 编写http接口api及接口自动化测试

使用机器人框架手动输入(验证码)?

浅析Minium,微信小程序自动化测试框架