(一)自动化安装依赖方式
1、把依赖的文件列表与已安装的列表对比,没有的就安装
2、执行用例之前就安装一遍依赖
3、判断依赖文件内容有没有变化,如果有变化就执行一次安装(推荐这种方式)
4、自动依赖脑图分析:
例如Windows系统,家目录的路径为:用户-->XXX
5、编写程序
参考实例:auto_install.py
# pip itall -r requriments.txt # pip freeze > requirements_local.txt # pip install xxx import hashlib import os user_home = os.environ['USERPROFILE'] if os.environ.get('USERPROFILE') else os.environ.get('HOME') class InstallRequire: # 存放本地缓存的文件 local_file = os.path.join(user_home,'.requirments_cache') require_file = 'requirements.txt' command = 'pip install -r requirements.txt' @property def check_local_file(self): if os.path.exists(self.local_file): return True #静态方法 @staticmethod def read_file(file_name): with open(file_name,encoding='utf-8') as fr: return fr.read() @staticmethod def write_file(file_name,content): with open(file_name,'w',encoding='utf-8') as fr: return fr.write(content) @staticmethod def md5(msg): msg = str(msg) m =hashlib.md5(msg.encode()) return m.hexdigest() def main(self): require_file_md5 = self.md5(self.read_file(self.require_file)) if self.check_local_file: local_file_md5 = self.read_file(self.local_file) if local_file_md5 != require_file_md5: print('依赖文件有变化,开始安装!') os.system(self.command) self.write_file(self.local_file,require_file_md5) print('安装完成!') else: print('依赖文件没有变化,继续执行!') else: print('未发现本地缓存文件,开始安装依赖!') os.system(self.command) self.write_file(self.local_file, require_file_md5) if __name__ == '__main__': ir = InstallRequire() ir.main()
执行截图所示:
(二)多线程和多进程
1、进程和线程的含义
(1)进程:一个程序,它是一组资源的集合;一个进程里面默认是有一个线程的,主线程
(2)线程:最小的执行单位;线程和线程之间是互相独立的;主线程等待子线程执行结束;线程和线程之间,数据是共享的。
(3)一个进程包含多个线程
2、单线程例子:
import threading import time def clean(): print('打扫卫生') time.sleep(2) def wash_clothes(): print('洗衣服') time.sleep(3) def cook(): print('做饭') time.sleep(1) # 单线程的方式 clean() wash_clothes() cook()
3、多线程例一:
import threading import time def clean(): print('打扫卫生') time.sleep(2) def wash_clothes(): print('洗衣服') time.sleep(3) def cook(): print('做饭') time.sleep(1) # 多线程 start_time = time.time() t = threading.Thread(target=clean) t2 = threading.Thread(target=wash_clothes) t3 = threading.Thread(target=cook) t.start() t2.start() t3.start() t.join() t2.join() t3.join() end_time = time.time() print(end_time-start_time) print(threading.activeCount() ) # 获取当前的线程数
4、多线程例二:(Python的多线程是利用不了多核CPU的)
(1)等待多个子线程执行结束,把启动的子线程放到list中,再循环调用t.join
import threading import time import random def export_data(): print(threading.current_thread()) print('export_data') time.sleep(random.randint(1,5)) #1、等待多个子线程执行结束,把启动的子线程放到list中,再循环调用t.join thread_list = [] for i in range(10): t = threading.Thread(target=export_data) thread_list.append(t) t.start() for t in thread_list: t.join() print('数据都导完了')
(2)等待多个子线程执行结束,通过判断当前线程数
import threading import time import random def export_data(): print(threading.current_thread()) print('export_data') time.sleep(random.randint(1,5)) #2、等待多个子线程执行结束,通过判断当前线程数 for i in range(10): t = threading.Thread(target=export_data) t.start() while threading.active_count() !=1: pass print('数据都导完了')
5、函数传参
实例:多线程函数传参.py
import threading import requests import auto_install def down_load_pic(url): print('开始下载',url) r = requests.get(url) file_name = auto_install.InstallRequire.md5(url) + '.jpg' with open(file_name,'wb') as fw: fw.write(r.content) urls = [ 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606147468333&di=20d70afc041d614c95ed4bece923a30f&imgtype=0&src=http%3A%2F%2Fimage.biaobaiju.com%2Fuploads%2F20190624%2F14%2F1561358630-nlyshjVPwq.jpg', 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2726503334,2969167273&fm=26&gp=0.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606147501828&di=53cbd54babc4a99fcfe1f8da6ecee1f5&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fforum%2Fw%3D580%2Fsign%3D1f6bfa4da06eddc426e7b4f309dab6a2%2F7b4a031b0ef41bd5bcf13c5c5cda81cb38db3d48.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606147467777&di=2d4404ca7bc33c49c008749a6d4a04c2&imgtype=0&src=http%3A%2F%2Fimg.article.pchome.net%2F00%2F25%2F47%2F87%2Fpic_lib%2Fs960x639%2F004s960x639.jpg' ] for url in urls: t = threading.Thread(target=down_load_pic,args=[url]) t.start() while threading.active_count()!=1: pass print('所有图片下载完成!')
6、线程锁:多个线程同时操作同一个数据的时候,需要加锁和解锁:(线程安全)
import threading count = 0 lock = threading.Lock() def add(): global count for i in range(10000): # # 加锁 # lock.acquire() # count+=1 # #解锁 # lock.release() with lock: count +=1 for i in range(2): t = threading.Thread(target=add) t.start() while threading.active_count() !=1: pass print(count)
7、守护线程:守护主线程,只要主线程结束,不管子线程有没有执行完成,全部都结束。
import threading import time import random def talk(name): print('正在和%s聊天'%name) time.sleep(random.randint(1,5)) print('和%s聊完了'%name) t = threading.Thread(target=talk,args=['Nancy']) # 设成守护线程 t.setDaemon(True) t.start() t = threading.Thread(target=talk,args=['Mick']) # 设成守护线程 t.setDaemon(True) t.start() t = threading.Thread(target=talk,args=['Bob']) # 设成守护线程 t.setDaemon(True) t.start() t = threading.Thread(target=talk,args=['Bill']) # 设成守护线程 t.setDaemon(True) t.start() while threading.active_count() !=1: pass print('退出QQ')
8、队列:异步处理,保证顺序
(1)队列.py
#队列 list:异步化处理 import queue import random import time import threading orders_q = queue.Queue() # 生产者/消费者模式 def producer(): for i in range(100): order_id = random.randint(1,999) print('订单生成,orderid:%s'%order_id) orders_q.put(order_id) time.sleep(1) def consumer(): while True : if orders_q.qsize() >0: order_id = orders_q.get() print('订单落库',order_id) t = threading.Thread(target=producer) t.start() t = threading.Thread(target=consumer) t.start()
(2)队列多个消费者:
import queue import random import time import threading orders_q = queue.Queue() # 生产者/消费者模式 def producer(): for i in range(100): order_id = random.randint(1,999) print('订单生成,orderid:%s'%order_id) orders_q.put(order_id) time.sleep(1) def consumer(): while True : if orders_q.qsize() >0: order_id = orders_q.get() print('consumer1,订单落库',order_id) def consumer2(): while True : if orders_q.qsize() >0: order_id = orders_q.get() print('consumer2,订单落库',order_id) t = threading.Thread(target=producer) t.start() t = threading.Thread(target=consumer) t.start() t = threading.Thread(target=consumer2) t.start()
9、线程池:
(1)需要安装线程池:
pip install threadpool
(2)线程池.py
import threadpool import requests import threading import auto_install import os def down_load_pics(url): print(threading.current_thread()) print('开始下载',url) r = requests.get(url) file_name = auto_install.InstallRequire.md5(url)+'.jpg' with open(os.path.join('imgs',file_name),'wb') as fw: fw.write(r.content) urls = [ 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606147468333&di=20d70afc041d614c95ed4bece923a30f&imgtype=0&src=http%3A%2F%2Fimage.biaobaiju.com%2Fuploads%2F20190624%2F14%2F1561358630-nlyshjVPwq.jpg', 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2726503334,2969167273&fm=26&gp=0.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606147501828&di=53cbd54babc4a99fcfe1f8da6ecee1f5&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fforum%2Fw%3D580%2Fsign%3D1f6bfa4da06eddc426e7b4f309dab6a2%2F7b4a031b0ef41bd5bcf13c5c5cda81cb38db3d48.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606147467777&di=2d4404ca7bc33c49c008749a6d4a04c2&imgtype=0&src=http%3A%2F%2Fimg.article.pchome.net%2F00%2F25%2F47%2F87%2Fpic_lib%2Fs960x639%2F004s960x639.jpg', 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2121278104,33728762&fm=26&gp=0.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000c=1606323851635&di=113f81718f7c64a36edb0c60908a5bee&imgtype=0&src=http%3A%2F%2Fwww.euro-premium.cn%2Fsites%2Fdefault%2Ffiles%2F2018%2F11%2F2018-11-22-601.jpg', 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=4220879049,1581383431&fm=26&gp=0.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606323851632&di=be753a1b6588ca82d7d198e4facc58a8&imgtype=0&src=http%3A%2F%2Fpic4.zhimg.com%2Fv2-3ea59767a18b17cefc2d47544f75cc73_b.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606323851618&di=6020618ac70ba8cc4ca5fb1cc95018d8&imgtype=0&src=http%3A%2F%2Fpic5.58cdn.com.cn%2Fp1%2Fbig%2Fn_v1bl2lwkd2dcwvr2gxjeiq_d4473fafff50fcf7.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606323851617&di=5b3ceb961409c8761669367ad4e989cd&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn12%2F276%2Fw575h501%2F20180707%2F95e8-hexfcvm1029102.png', 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2386572285,3431827098&fm=26&gp=0.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606324141028&di=25b7667857e706069b7332a4cd056ea6&imgtype=0&src=http%3A%2F%2Fwww.goupuzi.com%2Fnewatt%2FMon_1909%2F1_174217_f7de79ed750c14f.png', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606324141027&di=4698c9dba49ca37ee3cf317019c75031&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201807%2F31%2F20180731112006_owdwm.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606324141025&di=5603beb7e5b9cc233517b8224494591f&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F10488054738%2F1000.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606324141024&di=2ec0d81314a0d9d28b09c3a802033428&imgtype=0&src=http%3A%2F%2Fimg.mp.itc.cn%2Fupload%2F20170331%2F3152b322ec994c648b2187231403d13f_th.jpg', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606324141023&di=c841e552dcfd7700c35dca775c1f8cf1&imgtype=0&src=http%3A%2F%2Fimage.uczzd.cn%2F1473046732427962250.jpg%3Fid%3D0', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606324141019&di=0e5c216e09f1ad9158fbf5e38dd256ac&imgtype=0&src=http%3A%2F%2Fspider.ws.126.net%2Fc5638d86b52baf7e3238309b3cc0731b.jpeg' ] pool = threadpool.ThreadPool(5) # 让它给每个线程分配数据 reqs = threadpool.makeRequests(down_load_pics,urls) #[pool.putRequest(req) for req in reqs] # 开始运行起来 for req in reqs: pool.putRequest(req) # 等待子线程执行结束 pool.wait() print('运行完成!')
(3)Python中有个全局解释器锁:GIL
(4)CPU密集型任务:消耗CPU比较多,例如数据的排序,通过运行计算之类的
(5)iOS密集型任务:input/output,例如写文件,读文件,上传下载操作
10、多进程:是可以利用多核CPU的
多进程.py
import multiprocessing # lock = multiprocessing.Lock() import time def make_money(): print('开始挣钱') time.sleep(10) def start_process(): for i in range(5): p = multiprocessing.Process(target=make_money) p.start() print(multiprocessing.active_children()) while len(multiprocessing.active_children()) !=1: pass print('运行结束!') if __name__ == '__main__': start_process()
(三)unittest基本使用
1、单元测试
(1)自己测试自己写的代码
单元测试框架:自动校验结果
unittest
pytest
(2)如何写测试用例
(3)如何查找测试用例:unittest.defaultTestLoader.discover()
# 查找某个目录下的测试用例 test_suite = unittest.defaultTestLoader.discover('cases','test*.py')
注:如果cases在别的其他路径下,而不在当前路径,直接将case文件夹名称改为绝对路径
(4)参数化
需要安装一个模块:
pip install parameterized
实例如下:
# 参数化 @parameterized.parameterized.expand( [ [1,2,3,'参数化第一条'], [-1,2,1,'参数化第二条'], [-1,2,2,'参数化第三条'], ] ) def test_param_add(self,a,b,c,desc): self._testMethodDoc = desc self.desc = desc result = add(a,b) self.assertEqual(c,result,'预期是%s,实际结果是%s'%(c,result))
(5)产生测试报告
实例1:my_function
def write_file(file_name,content): with open(file_name,'w',encoding='utf-8') as fw: fw.write(content) def add(a,b): return a+b if __name__ == '__main__': print(add(1,2)) print(add(-1, 2)) print(add(-1, -2)) print(add(-1.1, -2.0)) print(add(-1.1, 2))
引用模块:HTMLTestRunner.py
""" A TestRunner for use with the Python unit testing framework. It generates a HTML report to show the result at a glance. The simplest way to use this is to invoke its main method. E.g. import unittest import HTMLTestRunner ... define your tests ... if __name__ == '__main__': HTMLTestRunner.main() For more customization options, instantiates a HTMLTestRunner object. HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g. # output to a file fp = file('my_report.html', 'wb') runner = HTMLTestRunner.HTMLTestRunner( stream=fp, title='My unit test', description='This demonstrates the report output by HTMLTestRunner.' ) # Use an external stylesheet. # See the Template_mixin class for more customizable options runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">' # run the test runner.run(my_test_suite) ------------------------------------------------------------------------ Copyright (c) 2004-2007, Wai Yip Tung All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Wai Yip Tung nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # URL: http://tungwaiyip.info/software/HTMLTestRunner.html __author__ = "Wai Yip Tung" __version__ = "0.8.2" """ Change History Version 0.8.2 * Show output inline instead of popup window (Viorel Lupu). Version in 0.8.1 * Validated XHTML (Wolfgang Borgert). * Added description of test classes and test cases. Version in 0.8.0 * Define Template_mixin class for customization. * Workaround a IE 6 bug that it does not treat <script> block as CDATA. Version in 0.7.1 * Back port to Python 2.3 (Frank Horowitz). * Fix missing scroll bars in detail log (Podi). """ # TODO: color stderr # TODO: simplify javascript using ,ore than 1 class in the class attribute? import datetime import io import sys import time import unittest from xml.sax import saxutils # ------------------------------------------------------------------------ # The redirectors below are used to capture output during testing. Output # sent to sys.stdout and sys.stderr are automatically captured. However # in some cases sys.stdout is already cached before HTMLTestRunner is # invoked (e.g. calling logging.basicConfig). In order to capture those # output, use the redirectors for the cached stream. # # e.g. # >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector) # >>> class OutputRedirector(object): """ Wrapper to redirect stdout or stderr """ def __init__(self, fp): self.fp = fp def write(self, s): self.fp.write(s) def writelines(self, lines): self.fp.writelines(lines) def flush(self): self.fp.flush() stdout_redirector = OutputRedirector(sys.stdout) stderr_redirector = OutputRedirector(sys.stderr) # ---------------------------------------------------------------------- # Template class Template_mixin(object): """ Define a HTML template for report customerization and generation. Overall structure of an HTML report HTML +------------------------+ |<html> | | <head> | | | | STYLESHEET | | +----------------+ | | | | | | +----------------+ | | | | </head> | | | | <body> | | | | HEADING | | +----------------+ | | | | | | +----------------+ | | | | REPORT | | +----------------+ | | | | | | +----------------+ | | | | ENDING | | +----------------+ | | | | | | +----------------+ | | | | </body> | |</html> | +------------------------+ """ STATUS = { 0: 'pass', 1: 'fail', 2: 'error', } DEFAULT_TITLE = 'Unit Test Report' DEFAULT_DESCRIPTION = '' # ------------------------------------------------------------------------ # HTML Template HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>%(title)s</title> <meta name="generator" content="%(generator)s"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> %(stylesheet)s </head> <body> <script language="javascript" type="text/javascript"><!-- output_list = Array(); /* level - 0:Summary; 1:Failed; 2:All */ function showCase(level) { trs = document.getElementsByTagName("tr"); for (var i = 0; i < trs.length; i++) { tr = trs[i]; id = tr.id; if (id.substr(0,2) == 'ft') { if (level < 1) { tr.className = 'hiddenRow'; } else { tr.className = ''; } } if (id.substr(0,2) == 'pt') { if (level > 1) { tr.className = ''; } else { tr.className = 'hiddenRow'; } } } } function showClassDetail(cid, count) { var id_list = Array(count); var toHide = 1; for (var i = 0; i < count; i++) { tid0 = 't' + cid.substr(1) + '.' + (i+1); tid = 'f' + tid0; tr = document.getElementById(tid); if (!tr) { tid = 'p' + tid0; tr = document.getElementById(tid); } id_list[i] = tid; if (tr.className) { toHide = 0; } } for (var i = 0; i < count; i++) { tid = id_list[i]; if (toHide) { document.getElementById('div_'+tid).style.display = 'none' document.getElementById(tid).className = 'hiddenRow'; } else { document.getElementById(tid).className = ''; } } } function showTestDetail(div_id){ var details_div = document.getElementById(div_id) var displayState = details_div.style.display // alert(displayState) if (displayState != 'block' ) { displayState = 'block' details_div.style.display = 'block' } else { details_div.style.display = 'none' } } function html_escape(s) { s = s.replace(/&/g,'&'); s = s.replace(/</g,'<'); s = s.replace(/>/g,'>'); return s; } /* obsoleted by detail in <div> function showOutput(id, name) { var w = window.open("", //url name, "resizable,scrollbars,status,width=800,height=450"); d = w.document; d.write("<pre>"); d.write(html_escape(output_list[id])); d.write(" "); d.write("<a href='javascript:window.close()'>close</a> "); d.write("</pre> "); d.close(); } */ --></script> %(heading)s %(report)s %(ending)s </body> </html> """ # variables: (title, generator, stylesheet, heading, report, ending) # ------------------------------------------------------------------------ # Stylesheet # # alternatively use a <link> for external style sheet, e.g. # <link rel="stylesheet" href="$url" type="text/css"> STYLESHEET_TMPL = """ <style type="text/css" media="screen"> body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; } table { font-size: 100%; } pre { } /* -- heading ---------------------------------------------------------------------- */ h1 { font-size: 16pt; color: gray; } .heading { margin-top: 0ex; margin-bottom: 1ex; } .heading .attribute { margin-top: 1ex; margin-bottom: 0; } .heading .description { margin-top: 4ex; margin-bottom: 6ex; } /* -- css div popup ------------------------------------------------------------------------ */ a.popup_link { } a.popup_link:hover { color: red; } .popup_window { display: none; position: relative; left: 0px; top: 0px; /*border: solid #627173 1px; */ padding: 10px; background-color: #E6E6D6; font-family: "Lucida Console", "Courier New", Courier, monospace; text-align: left; font-size: 8pt; 500px; } } /* -- report ------------------------------------------------------------------------ */ #show_detail_line { margin-top: 3ex; margin-bottom: 1ex; } #result_table { 80%; border-collapse: collapse; border: 1px solid #777; } #header_row { font-weight: bold; color: white; background-color: #777; } #result_table td { border: 1px solid #777; padding: 2px; } #total_row { font-weight: bold; } .passClass { background-color: #6c6; } .failClass { background-color: #c60; } .errorClass { background-color: #c00; } .passCase { color: #6c6; } .failCase { color: #c60; font-weight: bold; } .errorCase { color: #c00; font-weight: bold; } .hiddenRow { display: none; } .testcase { margin-left: 2em; } /* -- ending ---------------------------------------------------------------------- */ #ending { } </style> """ # ------------------------------------------------------------------------ # Heading # HEADING_TMPL = """<div class='heading'> <h1>%(title)s</h1> %(parameters)s <p class='description'>%(description)s</p> </div> """ # variables: (title, parameters, description) HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p> """ # variables: (name, value) # ------------------------------------------------------------------------ # Report # REPORT_TMPL = """ <p id='show_detail_line'>Show <a href='javascript:showCase(0)'>Summary</a> <a href='javascript:showCase(1)'>Failed</a> <a href='javascript:showCase(2)'>All</a> </p> <table id='result_table'> <colgroup> <col align='left' /> <col align='right' /> <col align='right' /> <col align='right' /> <col align='right' /> <col align='right' /> </colgroup> <tr id='header_row'> <td>Test Group/Test case</td> <td>Count</td> <td>Pass</td> <td>Fail</td> <td>Error</td> <td>View</td> </tr> %(test_list)s <tr id='total_row'> <td>Total</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td> </td> </tr> </table> """ # variables: (test_list, count, Pass, fail, error) REPORT_CLASS_TMPL = r""" <tr class='%(style)s'> <td>%(desc)s</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td> </tr> """ # variables: (style, desc, count, Pass, fail, error, cid) REPORT_TEST_WITH_OUTPUT_TMPL = r""" <tr id='%(tid)s' class='%(Class)s'> <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> <td colspan='5' align='center'> <!--css div popup start--> <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" > %(status)s</a> <div id='div_%(tid)s' class="popup_window"> <div style='text-align: right; color:red;cursor:pointer'> <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " > [x]</a> </div> <pre> %(script)s </pre> </div> <!--css div popup end--> </td> </tr> """ # variables: (tid, Class, style, desc, status) REPORT_TEST_NO_OUTPUT_TMPL = r""" <tr id='%(tid)s' class='%(Class)s'> <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> <td colspan='5' align='center'>%(status)s</td> </tr> """ # variables: (tid, Class, style, desc, status) REPORT_TEST_OUTPUT_TMPL = r""" %(id)s: %(output)s """ # variables: (id, output) # ------------------------------------------------------------------------ # ENDING # ENDING_TMPL = """<div id='ending'> </div>""" # -------------------- The end of the Template class ------------------- TestResult = unittest.TestResult class _TestResult(TestResult): # note: _TestResult is a pure representation of results. # It lacks the output and reporting ability compares to unittest._TextTestResult. def __init__(self, verbosity=1): TestResult.__init__(self) self.stdout0 = None self.stderr0 = None self.success_count = 0 self.failure_count = 0 self.error_count = 0 self.verbosity = verbosity # result is a list of result in 4 tuple # ( # result code (0: success; 1: fail; 2: error), # TestCase object, # Test output (byte string), # stack trace, # ) self.result = [] def startTest(self, test): TestResult.startTest(self, test) # just one buffer for both stdout and stderr self.outputBuffer = io.StringIO() stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.stdout0 = sys.stdout self.stderr0 = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector def complete_output(self): """ Disconnect output redirection and return buffer. Safe to call multiple times. """ if self.stdout0: sys.stdout = self.stdout0 sys.stderr = self.stderr0 self.stdout0 = None self.stderr0 = None return self.outputBuffer.getvalue() def stopTest(self, test): # Usually one of addSuccess, addError or addFailure would have been called. # But there are some path in unittest that would bypass this. # We must disconnect stdout in stopTest(), which is guaranteed to be called. self.complete_output() def addSuccess(self, test): self.success_count += 1 TestResult.addSuccess(self, test) output = self.complete_output() self.result.append((0, test, output, '')) if self.verbosity > 1: sys.stderr.write('ok ') sys.stderr.write(str(test)) sys.stderr.write(' ') else: sys.stderr.write('.') def addError(self, test, err): self.error_count += 1 TestResult.addError(self, test, err) _, _exc_str = self.errors[-1] output = self.complete_output() self.result.append((2, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('E ') sys.stderr.write(str(test)) sys.stderr.write(' ') else: sys.stderr.write('E') def addFailure(self, test, err): self.failure_count += 1 TestResult.addFailure(self, test, err) _, _exc_str = self.failures[-1] output = self.complete_output() self.result.append((1, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(test)) sys.stderr.write(' ') else: sys.stderr.write('F') class HTMLTestRunner(Template_mixin): """ """ def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None): self.stream = stream self.verbosity = verbosity if title is None: self.title = self.DEFAULT_TITLE else: self.title = title if description is None: self.description = self.DEFAULT_DESCRIPTION else: self.description = description self.startTime = datetime.datetime.now() def run(self, test): "Run the given test case or test suite." result = _TestResult(self.verbosity) test(result) self.stopTime = datetime.datetime.now() self.generateReport(test, result) # print >> sys.stderr, ' Time Elapsed: %s' % (self.stopTime-self.startTime) print(sys.stderr, ' Time Elapsed: %s' % (self.stopTime-self.startTime)) return result def sortResult(self, result_list): # unittest does not seems to run in any particular order. # Here at least we want to group them together by class. rmap = {} classes = [] for n,t,o,e in result_list: cls = t.__class__ if not cls in rmap: rmap[cls] = [] classes.append(cls) rmap[cls].append((n,t,o,e)) r = [(cls, rmap[cls]) for cls in classes] return r def getReportAttributes(self, result): """ Return report attributes as a list of (name, value). Override this to add custom attributes. """ startTime = str(self.startTime)[:19] duration = str(self.stopTime - self.startTime) status = [] if result.success_count: status.append('Pass %s' % result.success_count) if result.failure_count: status.append('Failure %s' % result.failure_count) if result.error_count: status.append('Error %s' % result.error_count ) if status: status = ' '.join(status) else: status = 'none' return [ ('Start Time', startTime), ('Duration', duration), ('Status', status), ] def generateReport(self, test, result): report_attrs = self.getReportAttributes(result) generator = 'HTMLTestRunner %s' % __version__ stylesheet = self._generate_stylesheet() heading = self._generate_heading(report_attrs) report = self._generate_report(result) ending = self._generate_ending() output = self.HTML_TMPL % dict( title = saxutils.escape(self.title), generator = generator, stylesheet = stylesheet, heading = heading, report = report, ending = ending, ) self.stream.write(output.encode('utf8')) def _generate_stylesheet(self): return self.STYLESHEET_TMPL def _generate_heading(self, report_attrs): a_lines = [] for name, value in report_attrs: line = self.HEADING_ATTRIBUTE_TMPL % dict( name = saxutils.escape(name), value = saxutils.escape(value), ) a_lines.append(line) heading = self.HEADING_TMPL % dict( title = saxutils.escape(self.title), parameters = ''.join(a_lines), description = saxutils.escape(self.description), ) return heading def _generate_report(self, result): rows = [] sortedResult = self.sortResult(result.result) for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class np = nf = ne = 0 for n,t,o,e in cls_results: if n == 0: np += 1 elif n == 1: nf += 1 else: ne += 1 # format class description if cls.__module__ == "__main__": name = cls.__name__ else: name = "%s.%s" % (cls.__module__, cls.__name__) doc = cls.__doc__ and cls.__doc__.split(" ")[0] or "" desc = doc and '%s: %s' % (name, doc) or name row = self.REPORT_CLASS_TMPL % dict( style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', desc = desc, count = np+nf+ne, Pass = np, fail = nf, error = ne, cid = 'c%s' % (cid+1), ) rows.append(row) for tid, (n,t,o,e) in enumerate(cls_results): self._generate_report_test(rows, cid, tid, n, t, o, e) report = self.REPORT_TMPL % dict( test_list = ''.join(rows), count = str(result.success_count+result.failure_count+result.error_count), Pass = str(result.success_count), fail = str(result.failure_count), error = str(result.error_count), ) return report def _generate_report_test(self, rows, cid, tid, n, t, o, e): # e.g. 'pt1.1', 'ft1.1', etc has_output = bool(o or e) tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1) name = t.id().split('.')[-1] doc = t.shortDescription() or "" desc = doc and ('%s: %s' % (name, doc)) or name tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL # o and e should be byte string because they are collected from stdout and stderr? if isinstance(o,str): # TODO: some problem with 'string_escape': it escape and mess up formating # uo = unicode(o.encode('string_escape')) # uo = o.decode('latin-1') uo = e else: uo = o if isinstance(e,str): # TODO: some problem with 'string_escape': it escape and mess up formating # ue = unicode(e.encode('string_escape')) # ue = e.decode('latin-1') ue = e else: ue = e script = self.REPORT_TEST_OUTPUT_TMPL % dict( id = tid, output = saxutils.escape(str(uo)+ue), ) row = tmpl % dict( tid = tid, Class = (n == 0 and 'hiddenRow' or 'none'), style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'), desc = desc, script = script, status = self.STATUS[n], ) rows.append(row) if not has_output: return def _generate_ending(self): return self.ENDING_TMPL ############################################################################## # Facilities for running tests from the command line ############################################################################## # Note: Reuse unittest.TestProgram to launch test. In the future we may # build our own launcher to support more specific command line # parameters like test title, CSS, etc. class TestProgram(unittest.TestProgram): """ A variation of the unittest.TestProgram. Please refer to the base class for command line parameters. """ def runTests(self): # Pick HTMLTestRunner as the default test runner. # base class's testRunner parameter is not useful because it means # we have to instantiate HTMLTestRunner before we know self.verbosity. if self.testRunner is None: self.testRunner = HTMLTestRunner(verbosity=self.verbosity) unittest.TestProgram.runTests(self) main = TestProgram ############################################################################## # Executing this module from the command line ############################################################################## if __name__ == "__main__": main(module=None)
实例2:单元测试.py
import unittest import my_function import HTMLTestRunner class TestAdd(unittest.TestCase): def test_add_normal(self): result = my_function.add(1,2) self.assertEqual(3,result) def test_add_error1(self): result = my_function.add(1,2) self.assertEqual(4,result) def test_add_error2(self): result = my_function.add(1,2) self.assertEqual(4,result,'正常整数加法,没有通过') if __name__ == '__main__': # 不产生测试报告 # unittest.main() # 单个运行某个测试用例 # test_suite = unittest.TestSuite() # test_suite.addTest(TestAdd('test_add_error2')) # test_suite.addTest(TestAdd('test_add_error1')) # 运行某个类里面所有的测试用例 test_suite = unittest.makeSuite(TestAdd) with open('report.html','wb') as fw: runner = HTMLTestRunner.HTMLTestRunner(stream=fw,title='天马座测试报告', description='天马座接口测试报告', verbosity=2 ) runner.run(test_suite)
引用模块:HTMLTestRunnerNew.py
# -*- coding: utf-8 -*- """ A TestRunner for use with the Python unit testing framework. It generates a HTML report to show the result at a glance. The simplest way to use this is to invoke its main method. E.g. import unittest import HTMLTestRunner ... define your tests ... if __name__ == '__main__': HTMLTestRunner.main() For more customization options, instantiates a HTMLTestRunner object. HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g. # output to a file fp = file('my_report.html', 'wb') runner = HTMLTestRunner.HTMLTestRunner( stream=fp, title='My unit test', description='This demonstrates the report output by HTMLTestRunner.' ) # Use an external stylesheet. # See the Template_mixin class for more customizable options runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">' # run the test runner.run(my_test_suite) ------------------------------------------------------------------------ Copyright (c) 2004-2007, Wai Yip Tung All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Wai Yip Tung nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # URL: http://tungwaiyip.info/software/HTMLTestRunner.html __author__ = "Wai Yip Tung" __version__ = "0.9.1" """ Change History Version 0.9.1 * 用Echarts添加执行情况统计图 (灰蓝) Version 0.9.0 * 改成Python 3.x (灰蓝) Version 0.8.3 * 使用 Bootstrap稍加美化 (灰蓝) * 改为中文 (灰蓝) Version 0.8.2 * Show output inline instead of popup window (Viorel Lupu). Version in 0.8.1 * Validated XHTML (Wolfgang Borgert). * Added description of test classes and test cases. Version in 0.8.0 * Define Template_mixin class for customization. * Workaround a IE 6 bug that it does not treat <script> block as CDATA. Version in 0.7.1 * Back port to Python 2.3 (Frank Horowitz). * Fix missing scroll bars in detail log (Podi). """ # TODO: color stderr # TODO: simplify javascript using ,ore than 1 class in the class attribute? import datetime import sys import io import time import unittest from xml.sax import saxutils # ------------------------------------------------------------------------ # The redirectors below are used to capture output during testing. Output # sent to sys.stdout and sys.stderr are automatically captured. However # in some cases sys.stdout is already cached before HTMLTestRunner is # invoked (e.g. calling logging.basicConfig). In order to capture those # output, use the redirectors for the cached stream. # # e.g. # >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector) # >>> class OutputRedirector(object): """ Wrapper to redirect stdout or stderr """ def __init__(self, fp): self.fp = fp def write(self, s): self.fp.write(s) def writelines(self, lines): self.fp.writelines(lines) def flush(self): self.fp.flush() stdout_redirector = OutputRedirector(sys.stdout) stderr_redirector = OutputRedirector(sys.stderr) # ---------------------------------------------------------------------- # Template class Template_mixin(object): """ Define a HTML template for report customerization and generation. Overall structure of an HTML report HTML +------------------------+ |<html> | | <head> | | | | STYLESHEET | | +----------------+ | | | | | | +----------------+ | | | | </head> | | | | <body> | | | | HEADING | | +----------------+ | | | | | | +----------------+ | | | | REPORT | | +----------------+ | | | | | | +----------------+ | | | | ENDING | | +----------------+ | | | | | | +----------------+ | | | | </body> | |</html> | +------------------------+ """ STATUS = { 0: u'通过', 1: u'失败', 2: u'错误', } DEFAULT_TITLE = 'Unit Test Report' DEFAULT_DESCRIPTION = '' # ------------------------------------------------------------------------ # HTML Template HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>%(title)s</title> <meta name="generator" content="%(generator)s"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script> <!-- <script type="text/javascript" src="js/echarts.common.min.js"></script> --> %(stylesheet)s </head> <body> <script language="javascript" type="text/javascript"><!-- output_list = Array(); /* level - 0:Summary; 1:Failed; 2:All */ function showCase(level) { trs = document.getElementsByTagName("tr"); for (var i = 0; i < trs.length; i++) { tr = trs[i]; id = tr.id; if (id.substr(0,2) == 'ft') { if (level < 1) { tr.className = 'hiddenRow'; } else { tr.className = ''; } } if (id.substr(0,2) == 'pt') { if (level > 1) { tr.className = ''; } else { tr.className = 'hiddenRow'; } } } } function showClassDetail(cid, count) { var id_list = Array(count); var toHide = 1; for (var i = 0; i < count; i++) { tid0 = 't' + cid.substr(1) + '.' + (i+1); tid = 'f' + tid0; tr = document.getElementById(tid); if (!tr) { tid = 'p' + tid0; tr = document.getElementById(tid); } id_list[i] = tid; if (tr.className) { toHide = 0; } } for (var i = 0; i < count; i++) { tid = id_list[i]; if (toHide) { document.getElementById('div_'+tid).style.display = 'none' document.getElementById(tid).className = 'hiddenRow'; } else { document.getElementById(tid).className = ''; } } } function showTestDetail(div_id){ var details_div = document.getElementById(div_id) var displayState = details_div.style.display // alert(displayState) if (displayState != 'block' ) { displayState = 'block' details_div.style.display = 'block' } else { details_div.style.display = 'none' } } function html_escape(s) { s = s.replace(/&/g,'&'); s = s.replace(/</g,'<'); s = s.replace(/>/g,'>'); return s; } /* obsoleted by detail in <div> function showOutput(id, name) { var w = window.open("", //url name, "resizable,scrollbars,status,width=800,height=450"); d = w.document; d.write("<pre>"); d.write(html_escape(output_list[id])); d.write(" "); d.write("<a href='javascript:window.close()'>close</a> "); d.write("</pre> "); d.close(); } */ --></script> <div id="div_base"> %(heading)s %(report)s %(ending)s %(chart_script)s </div> </body> </html> """ # variables: (title, generator, stylesheet, heading, report, ending, chart_script) ECHARTS_SCRIPT = """ <script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('chart')); // 指定图表的配置项和数据 var option = { title : { text: '测试执行情况', x:'center' }, tooltip : { trigger: 'item', formatter: "{a} <br/>{b} : {c} ({d}%%)" }, color: ['#95b75d', 'grey', '#b64645'], legend: { orient: 'vertical', left: 'left', data: ['通过','失败','错误'] }, series : [ { name: '测试执行情况', type: 'pie', radius : '60%%', center: ['50%%', '60%%'], data:[ {value:%(Pass)s, name:'通过'}, {value:%(fail)s, name:'失败'}, {value:%(error)s, name:'错误'} ], itemStyle: { emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }; // 使用刚指定的配置项和数据显示图表。 myChart.setOption(option); </script> """ # variables: (Pass, fail, error) # ------------------------------------------------------------------------ # Stylesheet # # alternatively use a <link> for external style sheet, e.g. # <link rel="stylesheet" href="$url" type="text/css"> STYLESHEET_TMPL = """ <style type="text/css" media="screen"> body { font-family: Microsoft YaHei,Consolas,arial,sans-serif; font-size: 80%; } table { font-size: 100%; } pre { white-space: pre-wrap;word-wrap: break-word; } /* -- heading ---------------------------------------------------------------------- */ h1 { font-size: 16pt; color: gray; } .heading { margin-top: 0ex; margin-bottom: 1ex; } .heading .attribute { margin-top: 1ex; margin-bottom: 0; } .heading .description { margin-top: 2ex; margin-bottom: 3ex; } /* -- css div popup ------------------------------------------------------------------------ */ a.popup_link { } a.popup_link:hover { color: red; } .popup_window { display: none; position: relative; left: 0px; top: 0px; /*border: solid #627173 1px; */ padding: 10px; /*background-color: #E6E6D6; */ font-family: "Lucida Console", "Courier New", Courier, monospace; text-align: left; font-size: 8pt; /* 500px;*/ } } /* -- report ------------------------------------------------------------------------ */ #show_detail_line { margin-top: 3ex; margin-bottom: 1ex; } #result_table { 99%; } #header_row { font-weight: bold; color: #303641; background-color: #ebebeb; } #total_row { font-weight: bold; } .passClass { background-color: #bdedbc; } .failClass { background-color: #ffefa4; } .errorClass { background-color: #ffc9c9; } .passCase { color: #6c6; } .failCase { color: #FF6600; font-weight: bold; } .errorCase { color: #c00; font-weight: bold; } .hiddenRow { display: none; } .testcase { margin-left: 2em; } /* -- ending ---------------------------------------------------------------------- */ #ending { } #div_base { position:absolute; top:0%; left:5%; right:5%; auto; height: auto; margin: -15px 0 0 0; } </style> """ # ------------------------------------------------------------------------ # Heading # HEADING_TMPL = """ <div class='page-header'> <h1>%(title)s</h1> %(parameters)s </div> <div style="float: left;50%%;"><p class='description'>%(description)s</p></div> <div id="chart" style="50%%;height:400px;float:left;"></div> """ # variables: (title, parameters, description) HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p> """ # variables: (name, value) # ------------------------------------------------------------------------ # Report # REPORT_TMPL = u""" <div class="btn-group btn-group-sm"> <button class="btn btn-default" onclick='javascript:showCase(0)'>总结</button> <button class="btn btn-default" onclick='javascript:showCase(1)'>失败</button> <button class="btn btn-default" onclick='javascript:showCase(2)'>全部</button> </div> <p></p> <table id='result_table' class="table table-bordered"> <colgroup> <col align='left' /> <col align='right' /> <col align='right' /> <col align='right' /> <col align='right' /> <col align='right' /> </colgroup> <tr id='header_row'> <td>测试套件/测试用例</td> <td>总数</td> <td>通过</td> <td>失败</td> <td>错误</td> <td>查看</td> </tr> %(test_list)s <tr id='total_row'> <td>总计</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td> </td> </tr> </table> """ # variables: (test_list, count, Pass, fail, error) REPORT_CLASS_TMPL = u""" <tr class='%(style)s'> <td>%(desc)s</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">详情</a></td> </tr> """ # variables: (style, desc, count, Pass, fail, error, cid) REPORT_TEST_WITH_OUTPUT_TMPL = r""" <tr id='%(tid)s' class='%(Class)s'> <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> <td colspan='5' align='center'> <!--css div popup start--> <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" > %(status)s</a> <div id='div_%(tid)s' class="popup_window"> <pre>%(script)s</pre> </div> <!--css div popup end--> </td> </tr> """ # variables: (tid, Class, style, desc, status) REPORT_TEST_NO_OUTPUT_TMPL = r""" <tr id='%(tid)s' class='%(Class)s'> <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> <td colspan='5' align='center'>%(status)s</td> </tr> """ # variables: (tid, Class, style, desc, status) REPORT_TEST_OUTPUT_TMPL = r"""%(id)s: %(output)s""" # variables: (id, output) # ------------------------------------------------------------------------ # ENDING # ENDING_TMPL = """<div id='ending'> </div>""" # -------------------- The end of the Template class ------------------- TestResult = unittest.TestResult class _TestResult(TestResult): # note: _TestResult is a pure representation of results. # It lacks the output and reporting ability compares to unittest._TextTestResult. def __init__(self, verbosity=1): TestResult.__init__(self) self.stdout0 = None self.stderr0 = None self.success_count = 0 self.failure_count = 0 self.error_count = 0 self.verbosity = verbosity # result is a list of result in 4 tuple # ( # result code (0: success; 1: fail; 2: error), # TestCase object, # Test output (byte string), # stack trace, # ) self.result = [] self.subtestlist = [] def startTest(self, test): TestResult.startTest(self, test) # just one buffer for both stdout and stderr self.outputBuffer = io.StringIO() stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.stdout0 = sys.stdout self.stderr0 = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector def complete_output(self): """ Disconnect output redirection and return buffer. Safe to call multiple times. """ if self.stdout0: sys.stdout = self.stdout0 sys.stderr = self.stderr0 self.stdout0 = None self.stderr0 = None return self.outputBuffer.getvalue() def stopTest(self, test): # Usually one of addSuccess, addError or addFailure would have been called. # But there are some path in unittest that would bypass this. # We must disconnect stdout in stopTest(), which is guaranteed to be called. self.complete_output() def addSuccess(self, test): if test not in self.subtestlist: self.success_count += 1 TestResult.addSuccess(self, test) output = self.complete_output() self.result.append((0, test, output, '')) if self.verbosity > 1: sys.stderr.write('ok ') sys.stderr.write(str(test)) sys.stderr.write(' ') else: sys.stderr.write('.') def addError(self, test, err): self.error_count += 1 TestResult.addError(self, test, err) _, _exc_str = self.errors[-1] output = self.complete_output() self.result.append((2, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('E ') sys.stderr.write(str(test)) sys.stderr.write(' ') else: sys.stderr.write('E') def addFailure(self, test, err): self.failure_count += 1 TestResult.addFailure(self, test, err) _, _exc_str = self.failures[-1] output = self.complete_output() self.result.append((1, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(test)) sys.stderr.write(' ') else: sys.stderr.write('F') def addSubTest(self, test, subtest, err): if err is not None: if getattr(self, 'failfast', False): self.stop() if issubclass(err[0], test.failureException): self.failure_count += 1 errors = self.failures errors.append((subtest, self._exc_info_to_string(err, subtest))) output = self.complete_output() self.result.append((1, test, output + ' SubTestCase Failed: ' + str(subtest), self._exc_info_to_string(err, subtest))) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(subtest)) sys.stderr.write(' ') else: sys.stderr.write('F') else: self.error_count += 1 errors = self.errors errors.append((subtest, self._exc_info_to_string(err, subtest))) output = self.complete_output() self.result.append( (2, test, output + ' SubTestCase Error: ' + str(subtest), self._exc_info_to_string(err, subtest))) if self.verbosity > 1: sys.stderr.write('E ') sys.stderr.write(str(subtest)) sys.stderr.write(' ') else: sys.stderr.write('E') self._mirrorOutput = True else: self.subtestlist.append(subtest) self.subtestlist.append(test) self.success_count += 1 output = self.complete_output() self.result.append((0, test, output + ' SubTestCase Pass: ' + str(subtest), '')) if self.verbosity > 1: sys.stderr.write('ok ') sys.stderr.write(str(subtest)) sys.stderr.write(' ') else: sys.stderr.write('.') class HTMLTestRunner(Template_mixin): def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None): self.stream = stream self.verbosity = verbosity if title is None: self.title = self.DEFAULT_TITLE else: self.title = title if description is None: self.description = self.DEFAULT_DESCRIPTION else: self.description = description self.startTime = datetime.datetime.now() def run(self, test): "Run the given test case or test suite." result = _TestResult(self.verbosity) test(result) self.stopTime = datetime.datetime.now() self.generateReport(test, result) print(' Time Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr) return result def sortResult(self, result_list): # unittest does not seems to run in any particular order. # Here at least we want to group them together by class. rmap = {} classes = [] for n,t,o,e in result_list: cls = t.__class__ if cls not in rmap: rmap[cls] = [] classes.append(cls) rmap[cls].append((n,t,o,e)) r = [(cls, rmap[cls]) for cls in classes] return r def getReportAttributes(self, result): """ Return report attributes as a list of (name, value). Override this to add custom attributes. """ startTime = str(self.startTime)[:19] duration = str(self.stopTime - self.startTime) status = [] if result.success_count: status.append(u'通过 %s' % result.success_count) if result.failure_count: status.append(u'失败 %s' % result.failure_count) if result.error_count: status.append(u'错误 %s' % result.error_count ) if status: status = ' '.join(status) else: status = 'none' return [ (u'开始时间', startTime), (u'运行时长', duration), (u'状态', status), ] def generateReport(self, test, result): report_attrs = self.getReportAttributes(result) generator = 'HTMLTestRunner %s' % __version__ stylesheet = self._generate_stylesheet() heading = self._generate_heading(report_attrs) report = self._generate_report(result) ending = self._generate_ending() chart = self._generate_chart(result) output = self.HTML_TMPL % dict( title = saxutils.escape(self.title), generator = generator, stylesheet = stylesheet, heading = heading, report = report, ending = ending, chart_script = chart ) self.stream.write(output.encode('utf8')) def _generate_stylesheet(self): return self.STYLESHEET_TMPL def _generate_heading(self, report_attrs): a_lines = [] for name, value in report_attrs: line = self.HEADING_ATTRIBUTE_TMPL % dict( name = saxutils.escape(name), value = saxutils.escape(value), ) a_lines.append(line) heading = self.HEADING_TMPL % dict( title = saxutils.escape(self.title), parameters = ''.join(a_lines), description = saxutils.escape(self.description), ) return heading def _generate_report(self, result): rows = [] sortedResult = self.sortResult(result.result) for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class np = nf = ne = 0 for n,t,o,e in cls_results: if n == 0: np += 1 elif n == 1: nf += 1 else: ne += 1 # format class description if cls.__module__ == "__main__": name = cls.__name__ else: name = "%s.%s" % (cls.__module__, cls.__name__) doc = cls.__doc__ and cls.__doc__.split(" ")[0] or "" desc = doc and '%s: %s' % (name, doc) or name row = self.REPORT_CLASS_TMPL % dict( style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', desc = desc, count = np+nf+ne, Pass = np, fail = nf, error = ne, cid = 'c%s' % (cid+1), ) rows.append(row) for tid, (n,t,o,e) in enumerate(cls_results): self._generate_report_test(rows, cid, tid, n, t, o, e) report = self.REPORT_TMPL % dict( test_list = ''.join(rows), count = str(result.success_count+result.failure_count+result.error_count), Pass = str(result.success_count), fail = str(result.failure_count), error = str(result.error_count), ) return report def _generate_chart(self, result): chart = self.ECHARTS_SCRIPT % dict( Pass=str(result.success_count), fail=str(result.failure_count), error=str(result.error_count), ) return chart def _generate_report_test(self, rows, cid, tid, n, t, o, e): # e.g. 'pt1.1', 'ft1.1', etc has_output = bool(o or e) tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1) name = t.id().split('.')[-1] doc = t.shortDescription() or "" desc = doc and ('%s: %s' % (name, doc)) or name tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL script = self.REPORT_TEST_OUTPUT_TMPL % dict( id=tid, output=saxutils.escape(o+e), ) row = tmpl % dict( tid=tid, Class=(n == 0 and 'hiddenRow' or 'none'), style=(n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none')), desc=desc, script=script, status=self.STATUS[n], ) rows.append(row) if not has_output: return def _generate_ending(self): return self.ENDING_TMPL ############################################################################## # Facilities for running tests from the command line ############################################################################## # Note: Reuse unittest.TestProgram to launch test. In the future we may # build our own launcher to support more specific command line # parameters like test title, CSS, etc. class TestProgram(unittest.TestProgram): """ A variation of the unittest.TestProgram. Please refer to the base class for command line parameters. """ def runTests(self): # Pick HTMLTestRunner as the default test runner. # base class's testRunner parameter is not useful because it means # we have to instantiate HTMLTestRunner before we know self.verbosity. if self.testRunner is None: self.testRunner = HTMLTestRunner(verbosity=self.verbosity) unittest.TestProgram.runTests(self) main = TestProgram ############################################################################## # Executing this module from the command line ############################################################################## if __name__ == "__main__": main(module=None)
测试报告:饼图+列表
实例3:单元测试.py
import unittest import HTMLTestRunner import HTMLTestRunnerNew def add(a,b): return a+b class TestAdd(unittest.TestCase): '''测试add方法的类''' def test_add_normal(self): '''正常测试加法的''' result = add(1,2) self.assertEqual(3,result) def test_add_error1(self): '''测试失败使用''' result = add(1,2) self.assertEqual(4,result) def test_add_error2(self): '''测试失败有message的''' result = add(1,2) self.assertEqual(4,result,'正常整数加法,没有通过') if __name__ == '__main__': # 不产生测试报告 # unittest.main() # 单个运行某个测试用例 # test_suite = unittest.TestSuite() # test_suite.addTest(TestAdd('test_add_error2')) # test_suite.addTest(TestAdd('test_error1')) # 运行某个类里面所有的测试用例 test_suite = unittest.makeSuite(TestAdd) with open('report.html','wb') as fw: runner = HTMLTestRunnerNew.HTMLTestRunner(stream=fw,title='天马座测试报告', description='天马座接口测试报告', verbosity=2 ) runner.run(test_suite)
执行结果如下:
2、设置python文件模板(如图是Pycharm:)
(1)设置模板文件
(2)验证设置的Python模板是否生效:
进入画布页面,新建一个Python文件,自动带入设置的模板:
实例全部代码:单元测试.py
import unittest import HTMLTestRunner import HTMLTestRunnerNew import parameterized def add(a,b): return a+b class TestAdd(unittest.TestCase): '''测试add方法的类''' def test_add_normal(self): '''正常测试加法的''' result = add(1,2) self.assertEqual(3,result) def test_add_error1(self): '''测试失败使用''' result = add(1,2) self.assertEqual(4,result) def test_add_error2(self): '''测试失败有message的''' result = add(1,2) self.assertEqual(4,result,'正常整数加法,没有通过') # 参数化 @parameterized.parameterized.expand( [ [1,2,3,'参数化第一条'], [-1,2,1,'参数化第二条'], [-1,2,2,'参数化第三条'], ] ) def test_param_add(self,a,b,c,desc): self._testMethodDoc = desc self.desc = desc result = add(a,b) self.assertEqual(c,result,'预期是%s,实际结果是%s'%(c,result)) if __name__ == '__main__': # 不产生测试报告 # unittest.main() # 单个运行某个测试用例 # test_suite = unittest.TestSuite() # test_suite.addTest(TestAdd('test_add_error2')) # test_suite.addTest(TestAdd('test_error1')) # 运行某个类里面所有的测试用例 test_suite = unittest.makeSuite(TestAdd) # 查找某个目录下的测试用例 # test_suite = unittest.defaultTestLoader.discover('cases','test*.py') with open('report.html','wb') as fw: runner = HTMLTestRunnerNew.HTMLTestRunner(stream=fw,title='天马座测试报告', description='天马座接口测试报告', verbosity=2 ) runner.run(test_suite)