最近一个项目中需要由于输出的案例内容非常多(上万条),导致BeautifulReport输出的报告内容非常大(几百兆)。浏览器无法正常处理这么大的测试报告,就算打开了,也不方便阅读和处理,因此需要将报告分成多个输出。
经修改代码,发现单个进程内输出多个测试报告出现问题:第一个测试报告能正常数据对应加入到unittest.testSuite的中的案例,但是后面每一个报告会输出前面所有报告的案例。
如果进行多进程改造的话改造量又比较大,因此还是采用的单进程多线程方案。
经排查发现是BeautifulReport中用于存放测试结果的变量FIELDS是一个全局变量,因此就算新建多个BeautifulReport的实例也没办法正常输出每个实例中unittest.testSuite的报告内容,需修改BeautifulReport中关于FIELDS变量的使用。
结合unittest和BeautifulReport测试api接口的的使用代码如下:
1 #!/usr/bin/python 2 import os,sys 3 from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed 4 from multiprocessing import Process, Lock, Queue 5 import unittest 6 import time 7 import copy 8 sys.path.append(r'') 9 10 from BeautifulReport import BeautifulReport 11 12 def output_report(testsuit, filename, interface_name): 13 try: 14 log.info(f"output_report filename:{filename} interface_name:{interface_name}") 15 now = time.strftime("%Y-%m-%d_%H_%M_%S") 16 result = BeautifulReport(testsuit) 17 result.report(filename=now + "_" + filename + f"_{interface_name}" +'_测试报告.html', description='测试报告', log_path='../report') 18 19 log.info(f"output_report filename:{filename} interface_name:{interface_name} over") 20 print(f"******output_report filename:{filename} interface_name:{interface_name} over******") 21 22 return {'result':True} 23 except Exception as e: 24 print(f"output_report filename:{filename} interface_name:{interface_name} exception! error_info:{e}") 25 return {'result':False, 'interface_name':interface_name, 'error_info':e} 26 27 def demo_run(cases): 28 #省略一些准备代码 29 #...... 30 31 #多线程执行api接口测试任务 32 with ThreadPoolExecutor(max_workers=thread_num) as ts: 33 for case in cases: 34 if len(case['req']) == 1 and len(case['file_rsp']) != 1: 35 log.error(f"case_id:{case['TestCaseID']} file_rsp len:{len(case['file_rsp'])} != 1 error!") 36 continue 37 task.append(ts.submit(test.send_request, api, case)) 38 39 #收集任务返回结果 40 result_msg_list = [future.result() for future in as_completed(task)] 41 42 #将执行结果加入到testsuit中 43 #todo:输出报告如果太大内容太多导致输出时间长,则需要改造为多线程处理 44 test_suit_dict = {} 45 result_msg_list.sort(key=lambda x:x["TestCaseID"]) 46 for result_msg in result_msg_list: 47 interface_name = result_msg['interface_name'] 48 log.info(f"TestCaseID:{result_msg['TestCaseID']} interface_name:{interface_name} func_name:{result_msg['func_name']}") 49 50 if interface_name in test_suit_dict: 51 testsuit = test_suit_dict[interface_name] 52 testsuit.addTest(ParametrizedTestCase.parametrize(Test_Cash_Order_Api, "test_case_ATP", param=(result_msg,key))) 53 else: 54 testsuit = unittest.TestSuite() 55 test_suit_dict[interface_name] = testsuit 56 testsuit.addTest(ParametrizedTestCase.parametrize(Test_Cash_Order_Api, "test_case_ATP", param=(result_msg,key))) 57 58 #检查输出报告目录是否存在 59 if os.path.exists("../report") != True: 60 os.makedirs("../report") 61 62 #BeautifulReport输出testsuit中的比对结果 63 #注意这里输出多个测试报告需改造Beautifulreport源码中存放测试结果的FIELDS的使用,否则无法正常输出多个测试报告(每个测试报告会输出前面加入的测试集的结果) 64 #因为Beautifulreport源码中用的FIELDS变量是个全局变量,单个进程中加入到FIELDS中的测试结果会不断累计。需要将FIELDS变为类的成员变量。这样新建多个BeautifulReport的实例才能正常输出多个测试报告 65 #另外使用多进程输出多个测试报告比较难实现。需要多进程中使用功能的内容都是进程安全的,涉及到的logging等组件的使用都需要做改造。这里直接改造Beautifulreport中的FIELDS较为容易实现 66 for interface_name, testsuit in test_suit_dict.items(): 67 log.info(f"add process filename:{filename} interface_name:{interface_name}") 68 output_report(testsuit, filename, interface_name)
BeautifulReport中改造FIELDS的代码如下。左侧为改造前代码,右侧为改造后代码。思路是将FIELDS变为类的成员变量,而不是使用全局变量。
基类中初始化FIELDS为类成员变量:
输出的报告内容使用成员变量存储: