在稳定性测试中,将测试结果持续填加进入html报告
公司需要设计一个稳定性测试,就是一直持续的跑不同的用例,直到人为停止,用例基本完成,基本框架思路就是随机选择一个testcase,跑完后输出结果。但存在一个问题,现在的unittest或nose测试报告都是测完所有case后再输出html报告,再次运行,又生成新的,没法再原来的报告中再填加结果。
就这样问题,基本解决思路就是直接写个生成html报告的模块,然后再次每次结果加入。正好最近在看tempest的东西,里面有个这样的模块,那就不重复造轮子了,直接拿过来用吧。
先看看生成html_log.py文件
import shutil try: import xml.etree.cElementTree as ET except Exception: import xml.etree.ElementTree as ET TYPE_OK = 0 TYPE_ERROR = -1 TYPE_FAIL = -2 TYPE_SKIP = -3 class InfoParser(object): def __init__(self, file_path, info): self.file_path = file_path self.info = info def get_state(self): if self.info.find('... ok') != -1: return TYPE_OK elif self.info.find('... ERROR') != -1: return TYPE_ERROR elif self.info.find('... FAIL') != -1: return TYPE_FAIL elif self.info.find('... SKIP') != -1: return TYPE_SKIP def get_detail(self): if self.get_state() == 0: return '' else: offset = self.info.find('Traceback') if offset != -1: temp = self.info[offset:] return temp else: return '' def get_class_name(self): temp = self.get_testcase_name() offset = temp.rfind('.') if offset != -1: temp = temp[0: offset] return temp else: return '' def get_testcase_name(self): offset = self.info.find(' ...') if offset != -1: temp = self.info[0: offset] return temp return '' def create_new_overview_node(self, root, state, class_name): new_node = ET.Element('tr') root.insert(len(root.getchildren()) - 1, new_node) # testcase name sub_node = ET.SubElement(new_node, 'td') sub_node.text = class_name for x in range(5): sub_node = ET.SubElement(new_node, 'td') sub_node.text = '0' return new_node def add_overview_node_detail(self, item, state): item[5].text = str(int(item[5].text) + 1) if state == TYPE_FAIL: item[1].text = str(int(item[1].text) + 1) item[1].set('class', 'failed') elif state == TYPE_ERROR: item[2].text = str(int(item[2].text) + 1) item[2].set('class', 'failed') elif state == TYPE_SKIP: item[3].text = str(int(item[3].text) + 1) elif state == TYPE_OK: item[4].text = str(int(item[4].text) + 1) def add_to_overview(self, root, state, class_name): iter = root.getchildren() for item in iter: if item[0].text == class_name: self.add_overview_node_detail(item, state) # handle total total_item = root[len(root.getchildren()) - 1] self.add_overview_node_detail(total_item, state) return new_item = self.create_new_overview_node(root, state, class_name) self.add_overview_node_detail(new_item, state) # handle total total_item = root[len(root.getchildren()) - 1] self.add_overview_node_detail(total_item, state) def add_to_failure(self, root): new_item = ET.SubElement(root, 'section') # name sub_item = ET.SubElement(new_item, 'h3') sub_item.text = self.get_testcase_name() # details sub_item = ET.SubElement(new_item, 'div') sub_item.set('class', 'test-details') detail_sub_item = ET.SubElement(sub_item, 'h4') detail_sub_item.text = 'Detail' detail_sub_item = ET.SubElement(sub_item, 'pre') detail_sub_item.text = self.get_detail() def add_to_all_tests(self, root, state): new_item = ET.SubElement(root, 'li') sub_item = ET.SubElement(new_item, 'a') sub_item.text = self.get_testcase_name() if state == TYPE_FAIL or state == TYPE_ERROR: sub_item.set('class', 'failed') else: sub_item.set('class', 'success') def write_log(self): ret = self.get_state() tree = ET.parse(self.file_path) root = tree.getroot() # add overview self.add_to_overview( root.find('body').find('overview').find('section').find('table'), ret, self.get_class_name()) # add fail view if ret == TYPE_FAIL or ret == TYPE_ERROR: self.add_to_failure( root.find('body').find('failure_details'). find('section').find('div')) # add all tests self.add_to_all_tests( root.find('body').find('all_tests').find('section').find('ul'), ret) tree.write(self.file_path) def create_log_from_file(path, souce): shutil.copyfile(souce, path) def add_log(path, info): info_parser = InfoParser(path, info) info_parser.write_log() def format_testrunner_info(test, info, state): result = '' # handle name name = str(test) offset_b = name.find('(') offset_e = name.find(')') testcase_name = 'no name' if offset_b != -1 and offset_e != -1: testcase_name = name[offset_b + 1: offset_e] offset_e = name.find(' (') if offset_e != -1: testcase_name += '.' testcase_name += name[0: offset_e] result = testcase_name if state == TYPE_OK: result += ' ... ok\n\n' elif state == TYPE_ERROR: result += ' ... ERROR\n\n' elif state == TYPE_FAIL: result += ' ... FAIL\n\n' elif state == TYPE_SKIP: result += ' ... SKIP\n\n' result += info return result def add_testrunner_log(path, result, test_name): # success if result.wasSuccessful(): info = format_testrunner_info(test_name, 'Ran 1 test', TYPE_OK) #info = format_testrunner_info(name,'Ran 1 test',TYPE_OK) add_log(path, info) # fail for test, err in result.failures: info = format_testrunner_info(test, err, TYPE_FAIL) add_log(path, info) # error for name, err in result.errors: info = format_testrunner_info(name, err, TYPE_ERROR) add_log(path, info) # skip for test, reason in result.skipped: info = format_testrunner_info(test, reason, TYPE_SKIP) add_log(path, info)
使用非常简单,使用对应的空报告模板,使生成的结果调用add_testrunner_log写入空模板中,最后就可以持续生成报告了
空模板见下面的html代码,copy下来后存成html文件即可使用
<!DOCTYPE html> <html> <head> <title>Unit Test Report</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <style> body { font-family: Calibri, "Trebuchet MS", sans-serif; } * { word-break: break-all; } table, td, th, .dataid { border: 1px solid #aaa; border-collapse: collapse; background: #fff; } section { background: rgba(0, 0, 0, 0.05); margin: 2ex; padding: 1ex; border: 1px solid #999; border-radius: 5px; } h1 { font-size: 130%; } h2 { font-size: 120%; } h3 { font-size: 100%; } h4 { font-size: 85%; } h1, h2, h3, h4, a[href] { cursor: pointer; color: #0074d9; text-decoration: none; } h3 strong, a.failed { color: #ff4136; } .failed { color: #ff4136; } a.success { color: #3d9970; } pre { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', 'Monaco', 'Courier New', monospace; } .test-details, .traceback { display: none; } section:target .test-details { display: block; } </style> </head> <body> <overview> <h1>Overview</h1> <section> <table> <tr> <th>Class</th> <th class="failed">Fail</th> <th class="failed">Error</th> <th>Skip</th> <th>Success</th> <th>Total</th> </tr> <tr> <td><strong>Total</strong></td> <td>0</td> <td>0</td> <td>0</td> <td>0</td> <td>0</td> </tr> </table> </section> </overview> <failure_details> <h1>Failure details</h1> <section> <h2>Failure details</h2> <div> </div> </section> </failure_details> <all_tests> <h1>All tests</h1> <section> <h2>all tests</h2> <ul> </ul> </section> </all_tests> </body> <script> Array.prototype.forEach.call(document.querySelectorAll('h1, h2, h3, h4'), function(el) { el.addEventListener('click', function() { el.nextElementSibling.style.display = document.defaultView.getComputedStyle(el.nextElementSibling).display == 'none' ? 'block' : 'none'; }) }) </script>
使用unittest写个示范代码如下:
import unittest import time import sys import html_log import os import re import random class test(unittest.TestCase): def setUp(self): pass def test_0001(self): assert 1==1 def test_0002(self): assert 2==2 if __name__=='__main__': suite=unittest.TestLoader().loadTestsFromTestCase(test) testcases=list() listcasedir='.' testunit=unittest.TestSuite() #选择case for test_case in suite: print test_case f=re.match("(test_.*) \(__main__.test\)",str(test_case)) tt=f.group(1) testcases.append(test_case) test = random.choice(testcases) mySuite = unittest.TestSuite() mySuite.addTest(test) result = unittest.TextTestRunner().run(mySuite) #这里xx.html就是对应的空模板
if not os.path.exists('test_aa.html'): html_log.create_log_from_file('test_aa.html', 'xx.html')
#将结果持续加入到对应的html报告中 print test html_log.add_testrunner_log('test_aa.html', result, test)
结果如下,测试一直在进行,结果就一直会持续输入
Email:362299908@qq.com