• UI自动化测试框架搭建——标记性能较差用例


    在拿到性能数据后需要将数据利用起来,下面对性能数据进行分析

    实现:如果性能达到设定的阈值,那么这段时间执行的用例就是性能较差的用例

    确定阈值

    首先确定一个阈值来当做性能的告警值,暂定为以下算法

    1. # threshold 阈值
    2. # average 平均值
    3. # max 最大值 
    4. threshold = average + (max - average) * 0.8

    计算各项值

    在上一章已经拿到了性能数据,然后把他们插入到数据库中

    由于平台是Java的所以用Java编写

    构造一个HashMap来存放全部的数据

    Map<StringMap<StringList<Double>>> perfSummaryData = new HashMap<>();
    

    最终返回的数据类型为:

    1. {
    2. "cpu":{"system":[363,25,121.43243243243244,314.6864864864865],"idle":[761,4,537.3716216216217,716.2743243243243],"pic_cpu":[373,3.2,66.42430555555556,311.6848611111111],"device_cpu_rate":[904,36,245.6418918918919,772.3283783783784],"user":[652,11,124.20945945945945,546.4418918918919]},
    3. "mem":{"free_ram":[2707.73,1720.11,2044.2376923076922,2575.0315384615387],"pid_pss":[452.18,231.72,313.105,424.365],"total_ram":[5478.93,5478.93,5478.929999999999,5478.93]},
    4. "other":null,
    5. "power":{"current":[0,0,0,0],"tempreture":[36.8,29.9,33.69638554216868,36.17927710843373],"voltage":[4.34,4.13,4.284096385542169,4.328819277108433]},
    6. "pss":{"pssList":[],"java_heap":[],"pss_system":[],"native_heap":[]},
    7. "thread":{"threadList":[272,3,191.46308724832215,255.89261744966444]},
    8. "traffic":{"device_transport":[6283.15,433.69,3777.436774193548,5782.00735483871],"device_receive":[167860.62,11597.64,106851.58806451612,155658.81361290324],"device_total":[174143.77,12031.33,110629.02451612902,161440.8209032258],"pid_tx":[6283.15,474.8,3779.061612903226,5782.332322580645],"pid_total":[174143.71,13349.69,110675.72096774195,161450.11219354838],"pid_rx":[167860.55,12874.89,106896.65838709677,155667.77167741934]},
    9. "version":"微医用户版4.6.5-内部版本号:268-灰度-待发布-创建时间:2022-06-23 21:18:10-最近修改时间:2022-06-23 21:18:10"
    10. }

    依次解析每个版本每次执行的性能数据然后根据性能类型放到对应的List中

    然后计算一下最大、最小、平均、阈值

    1. floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).max().orElse(0.0));
    2. floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).min().orElse(0.0));
    3. floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).average().orElse(0.0));
    4. floats.add(floats.get(2) + 0.8 * (floats.get(0) - floats.get(2))); // 阈值

    完整代码

    1. @Override
    2. public List getPerfDataSummary(UiPerfPageQuery pageQuery) {
    3.     List perfData = uiReportMapper.getPerfData(pageQuery);
    4.     Map<String, Map<StringList<Double>>> perfSummaryData = new HashMap<>();
    5.     String[] cpuKeys = { "device_cpu_rate""user""system""idle""pic_cpu" };
    6.     String[] memKeys = { "total_ram""free_ram""pid_pss" };
    7.     String[] powerKeys = { "voltage""tempreture""current" };
    8.     String[] threadKeys = { "threadList" };
    9.     String[] pssKeys = { "pssList""java_heap""native_heap""pss_system" };
    10.     String[] trafficKeys = { "device_total""device_receive""device_transport""pid_rx""pid_tx",
    11.         "pid_total" };
    12.     String[][] allKeys = { cpuKeys, memKeys, powerKeys, threadKeys, pssKeys, trafficKeys };
    13.     perfData.forEach(item -> {
    14.         String version = item.getVersion();
    15.         if (!perfSummaryData.containsKey(version)) {
    16.             HashMap<StringList<Double>> perfVersion = new HashMap<>();
    17.             for (String[] keys : allKeys) {
    18.                 for (String key : keys) {
    19.                     perfVersion.put(key, new ArrayList<>());
    20.                 }
    21.             }
    22.             perfSummaryData.put(version, perfVersion);
    23.         }
    24.         Map<StringList<Double>> stringListMap = perfSummaryData.get(version);
    25.         List<String> cpus = JSONArray.parseArray(item.getCpu(), String.class);
    26.         List<String> mems = JSONArray.parseArray(item.getMem(), String.class);
    27.         List<String> powers = JSONArray.parseArray(item.getPower(), String.class);
    28.         List<String> threads = JSONArray.parseArray(item.getThread(), String.class);
    29.         List<String> psss = JSONArray.parseArray(item.getPss(), String.class);
    30.         List<String> traffics = JSONArray.parseArray(item.getTraffic(), String.class);
    31.         cpus.forEach(cpu -> {
    32.             List<String> oneCpuInfo = JSONArray.parseArray(cpu, String.class);
    33.             if (!oneCpuInfo.isEmpty()) {
    34.                 for (int i = 1; i < 6; i++) {
    35.                     List<Double> datalist = stringListMap.get(cpuKeys[i - 1]);
    36.                     datalist.add(Double.parseDouble(oneCpuInfo.get(i)));
    37.                 }
    38.             }
    39.         });
    40.         mems.forEach(mem -> {
    41.             List<String> oneMemInfo = JSONArray.parseArray(mem, String.class);
    42.             if (!oneMemInfo.isEmpty()) {
    43.                 for (int i = 1; i < 4; i++) {
    44.                     List<Double> datalist = stringListMap.get(memKeys[i - 1]);
    45.                     datalist.add(Double.parseDouble(oneMemInfo.get(i)));
    46.                 }
    47.             }
    48.         });
    49.         powers.forEach(power -> {
    50.             List<String> onePowerInfo = JSONArray.parseArray(power, String.class);
    51.             if (!onePowerInfo.isEmpty()) {
    52.                 for (int i = 1; i < 4; i++) {
    53.                     List<Double> datalist = stringListMap.get(powerKeys[i - 1]);
    54.                     datalist.add(Double.parseDouble(onePowerInfo.get(i)));
    55.                 }
    56.             }
    57.         });
    58.         threads.forEach(thread -> {
    59.             List<String> oneThreadInfo = JSONArray.parseArray(thread, String.class);
    60.             if (!oneThreadInfo.isEmpty()) {
    61.                 for (int i = 1; i < 2; i++) {
    62.                     List<Double> datalist = stringListMap.get(threadKeys[i - 1]);
    63.                     datalist.add(Double.parseDouble(oneThreadInfo.get(i)));
    64.                 }
    65.             }
    66.         });
    67.         psss.forEach(pss -> {
    68.             List<String> onePssInfo = JSONArray.parseArray(pss, String.class);
    69.             if (!onePssInfo.isEmpty()) {
    70.                 for (int i = 1; i < 5; i++) {
    71.                     List<Double> datalist = stringListMap.get(pssKeys[i - 1]);
    72.                     datalist.add(Double.parseDouble(onePssInfo.get(i)));
    73.                 }
    74.             }
    75.         });
    76.         traffics.forEach(traffic -> {
    77.             List<String> oneTrafficInfo = JSONArray.parseArray(traffic, String.class);
    78.             if (!oneTrafficInfo.isEmpty()) {
    79.                 for (int i = 1; i < 7; i++) {
    80.                     List<Double> datalist = stringListMap.get(trafficKeys[i - 1]);
    81.                     datalist.add(Double.parseDouble(oneTrafficInfo.get(i)));
    82.                 }
    83.             }
    84.         });
    85.     });
    86.     List uiPerfSummaryDos = new ArrayList<>();
    87.     List<Double> floats;
    88.     for (String key : perfSummaryData.keySet()) {
    89.         UiPerfSummaryDo uiPerfSummaryDo = new UiPerfSummaryDo();
    90.         Map<StringList<Double>> stringListMap = perfSummaryData.get(key);
    91.         uiPerfSummaryDo.setVersion(key);
    92.         for (String[] all_key : allKeys) {
    93.             HashMap<StringList<Double>> dataDict = new HashMap<>();
    94.             for (String key1 : all_key) {
    95.                 floats = new ArrayList<>();
    96.                 List<Double> floats1 = stringListMap.get(key1);
    97.                 if (!floats1.isEmpty()) {
    98.                     floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).max().orElse(0.0));
    99.                     floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).min().orElse(0.0));
    100.                     floats.add(floats1.stream().mapToDouble(e -> e).filter(e -> e > 0.0).average().orElse(0.0));
    101.                     floats.add(floats.get(2) + 0.8 * (floats.get(0) - floats.get(2))); // 阈值
    102.                 }
    103.                 dataDict.put(key1, floats);
    104.             }
    105.             if (Arrays.equals(all_key, cpuKeys)) {
    106.                 uiPerfSummaryDo.setCpu(dataDict);
    107.             }
    108.             if (Arrays.equals(all_key, memKeys)) {
    109.                 uiPerfSummaryDo.setMem(dataDict);
    110.             }
    111.             if (Arrays.equals(all_key, powerKeys)) {
    112.                 uiPerfSummaryDo.setPower(dataDict);
    113.             }
    114.             if (Arrays.equals(all_key, threadKeys)) {
    115.                 uiPerfSummaryDo.setThread(dataDict);
    116.             }
    117.             if (Arrays.equals(all_key, pssKeys)) {
    118.                 uiPerfSummaryDo.setPss(dataDict);
    119.             }
    120.             if (Arrays.equals(all_key, trafficKeys)) {
    121.                 uiPerfSummaryDo.setTraffic(dataDict);
    122.             }
    123.         }
    124.         uiPerfSummaryDos.add(uiPerfSummaryDo);
    125.     }
    126.     return uiPerfSummaryDos;
    127. }

    解析Allure报告

    拿到一个用例的开始时间和结束时间,方便确定用例执行的时间范围

    在13.UI自动化测试框架搭建-处理Allure报告数据中有提到如何拿到allure的内容

    1. data = {
    2.     "fullName": full_name,
    3.     "status": json_data.get("status"),
    4.     "labels": labels,
    5.     "start": json_data.get("start"0),
    6.     "stop": json_data.get("stop"0),
    7.     "duration": json_data.get("stop"0) - json_data.get("start"0),
    8.     "parameters": parameters,
    9.     "statusDetails": statusDetails1,
    10.     "kano_url"",".join(kano_url),
    11.     "steps"";".join(steps),
    12.     "desc"";"
    13. }

    添加了

    1. "start": json_data.get("start"0)
    2. "stop": json_data.get("stop"0)
    3. "desc"";"

    现在将用例执行的情况写入到一份csv文件中,方便与其他的性能数据进行比对

    1. # 拼接csv文件路径
    2. casesfile = os.path.join(PERF_PATH, 'cases.csv')
    3. # csv文件头部
    4. title = ["datetime""flag"] + list(allure_results[0].keys())
    5. with open(casesfile, "a+", encoding="utf-8") as f:
    6.     # 写入头部
    7.     csv.writer(f, lineterminator='\n').writerow(title)
    8.     for i in allure_results:
    9.         allure_data1 = [timeoperator.get_localtime(i['start'], "%Y-%m-%d %H-%M-%S"), True] + list(
    10.             i.values())
    11.         allure_data2 = [timeoperator.get_localtime(i['stop'], "%Y-%m-%d %H-%M-%S"), False] + list(
    12.             i.values())
    13.         csv.writer(f, lineterminator='\n').writerow(allure_data1)
    14.         csv.writer(f, lineterminator='\n').writerow(allure_data2)

    使得datetime等于start和stop,写入两遍,再使用flag来标记方便后面去除掉

    这样就得到一份按照时间排序的用例执行结果了

    1. datetime,flag,fullName,status,labels,start,stop,duration,parameters,statusDetails,kano_url,steps,desc
    2. 2022-08-01 14-04-44,True,src.cases_android.wy.test_health.TestMedicationReminder#test_close_medication_reminder,passed,微医APP_健康_用药提醒-关闭用药提醒(@钟鑫),1659333884508,1659333917256,32748,,"{'message': '', 'trace': ''}",,1-查看提醒状态;1.1-查看「用药提醒_已开启提醒」是否存在;2-关闭用药提醒;2.1-点击「用药提醒_提醒滑块1」;3-点击确认;3.1-点击「通用_确认关闭」;4-查看提醒状态;4.1-查看「用药提醒_已关闭提醒」是否存在;4.2-查看「用药提醒_已关闭提醒1」是否存在;5-打开用药提醒;5.1-点击「用药提醒_提醒滑块2」;5.2-点击「用药提醒_已关闭提醒1」;6-查看提醒状态;6.1-查看「用药提醒_已开启提醒」是否存在,;
    3. 2022-08-01 14-05-17,False,src.cases_android.wy.test_health.TestMedicationReminder#test_close_medication_reminder,passed,微医APP_健康_用药提醒-关闭用药提醒(@钟鑫),1659333884508,1659333917256,32748,,"{'message': '', 'trace': ''}",,1-查看提醒状态;1.1-查看「用药提醒_已开启提醒」是否存在;2-关闭用药提醒;2.1-点击「用药提醒_提醒滑块1」;3-点击确认;3.1-点击「通用_确认关闭」;4-查看提醒状态;4.1-查看「用药提醒_已关闭提醒」是否存在;4.2-查看「用药提醒_已关闭提醒1」是否存在;5-打开用药提醒;5.1-点击「用药提醒_提醒滑块2」;5.2-点击「用药提醒_已关闭提醒1」;6-查看提醒状态;6.1-查看「用药提醒_已开启提醒」是否存在,;
    4. 2022-08-01 13-24-32,True,src.cases_android.wy.test_me.TestSetting#test_entrance_text,passed,微医APP_个人中心_设置-子项入口文案检查(@钟鑫),1659331472953,1659331473076,123,'settings_message',"{'message': '', 'trace': ''}",,1-查看是否存在我_设置-消息通知;1.1-查看「我_设置-消息通知」是否存在,;
    5. 2022-08-01 13-24-33,False,src.cases_android.wy.test_me.TestSetting#test_entrance_text,passed,微医APP_个人中心_设置-子项入口文案检查(@钟鑫),1659331472953,1659331473076,123,'settings_message',"{'message': '', 'trace': ''}",,1-查看是否存在我_设置-消息通知;1.1-查看「我_设置-消息通知」是否存在,;

    数据比对

    读取用例执行结果

    读取文件后将它的datetimes设置为datetime类型,方便后面的排序

    1. def cases_handle(self, path=f"{PERF_PATH}/cases.csv"):
    2.     df = self.read_csv(path)
    3.     df['datetime'] = pd.to_datetime(df['datetime'], format="%Y-%m-%d %H-%M-%S")
    4.     df = df.sort_values("datetime", ascending=True)
    5.     return df

    编写比较函数

    1. def add_desc(self, **kwargs) -> [dict]:
    2.     ...

    **kwargs动态输入键值对来进行筛选

    记录原先的列

    base_title = list(cases_df.columns)
    

    先进行cpu性能数据的处理

    cpu = ['device_cpu_rate%''user%''system%''idle%''pid_cpu%']
    

    遍历输入的键值对,如果输入的key值加上%在cpu这个列表里面,那就筛选出cpu性能数据中符合条件的数据

    1. for k, v in kwargs.items():
    2.     if f"{k}%" in cpu:
    3.         name = f"{k}%"
    4.         df1 = cpu_df[cpu_df[name] > v]

    将两个表合并一下

    cases_df = pd.concat([df1, cases_df]).sort_values("datetime", ascending=True)
    

    拿到超过阈值的行号索引

    error_index = list(cases_df[cases_df[name] > 0].index)
    

    在超过阈值索引的附近几行添加描述

    1. for i in error_index:
    2.     cases_df.loc[i, 'desc'] += f"{k}超过阈值;"
    3.     try:
    4.         cases_df.loc[i - 1'desc'] += f"{k}超过阈值;"
    5.     except Exception:
    6.         pass
    7.     try:
    8.         cases_df.loc[i + 1'desc'] += f"{k}超过阈值;"
    9.     except Exception:
    10.         pass

    剔除性能数据的列

    cases_df = cases_df[pd.notnull(cases_df['fullName'])]
    

    根据flag去除重复添加的用例信息

    cases_df = cases_df[cases_df['flag'] == True]
    

    根据之前保留的原始列信息将性能数据列去除

    new_df = pd.DataFrame(cases_df, columns=base_title)
    

    返回[dict]格式

    return new_df.to_dict("records")
    

    全部新增代码

    1. def cases_handle(self, path=f"{PERF_PATH}/cases.csv"):
    2.     df = self.read_csv(path)
    3.     df['datetime'] = pd.to_datetime(df['datetime'], format="%Y-%m-%d %H-%M-%S")
    4.     df = df.sort_values("datetime", ascending=True)
    5.     return df
    6. def add_desc(self, **kwargs) -> [dict]:
    7.     cpu = ['device_cpu_rate%''user%''system%''idle%''pid_cpu%']
    8.     mem = ['total_ram(MB)''free_ram(MB)''pid_pss(MB)']
    9.     power = ['voltage(V)''tempreture(C)''current(mA)']
    10.     traffic = ['device_total(KB)''device_receive(KB)',
    11.                'device_transport(KB)',
    12.                'pid_rx(KB)''pid_tx(KB)''pid_total(KB)']
    13.     cpu_df = self.cpu_handle()
    14.     mem_df = self.mem_handle()
    15.     power_df = self.power_handle()
    16.     thread_df = self.thread_num_handle()
    17.     traffic_df = self.traffic_handle()
    18.     cases_df = self.cases_handle()
    19.     base_title = list(cases_df.columns)
    20.     for k, v in kwargs.items():
    21.         if f"{k}%" in cpu:
    22.             name = f"{k}%"
    23.             df1 = cpu_df[cpu_df[name] > v]
    24.         if f"{k}(MB)" in mem:
    25.             name = f"{k}(MB)"
    26.             df1 = mem_df[mem_df[name] > v]
    27.         if any([f"{k}({i})" in power for i in ['V''C''mA']]):
    28.             if k == "voltage":
    29.                 name = f"{k}(V)"
    30.                 df1 = power_df[power_df[name] > v]
    31.             if k == "tempreture":
    32.                 name = f"{k}(C)"
    33.                 df1 = power_df[power_df[name] > v]
    34.             if k == "current":
    35.                 name = f"{k}(mA)"
    36.                 df1 = power_df[power_df[name] > v]
    37.         if k == "threadList":
    38.             name = "thread_num"
    39.             df1 = thread_df[thread_df[name] > v]
    40.         if f'{k}(KB)' in traffic:
    41.             name = f'{k}(KB)'
    42.             df1 = traffic_df[traffic_df[name] > v]
    43.         cases_df = pd.concat([df1, cases_df]).sort_values("datetime", ascending=True)
    44.         error_index = list(cases_df[cases_df[name] > 0].index)
    45.         for i in error_index:
    46.             cases_df.loc[i, 'desc'] += f"{k}超过阈值;"
    47.             try:
    48.                 cases_df.loc[i - 1'desc'] += f"{k}超过阈值;"
    49.             except Exception:
    50.                 pass
    51.             try:
    52.                 cases_df.loc[i + 1'desc'] += f"{k}超过阈值;"
    53.             except Exception:
    54.                 pass
    55.         cases_df = cases_df[pd.notnull(cases_df['fullName'])]
    56.     cases_df = cases_df[cases_df['flag'] == True]
    57.     new_df = pd.DataFrame(cases_df, columns=base_title)
    58.     return new_df.to_dict("records")

    测试

    1. cases_data = d.add_desc(
    2.         device_cpu_rate=700user=125system=300, idle=700, pid_cpu=300,
    3.         free_ram=2575, pid_pss=424, total_ram=5478,
    4.         tempreture=36, voltage=4,
    5.         threadList=255,
    6.         device_transport=5782, device_receive=155658, device_total=161440
    7.         pid_tx=5782, pid_total=161450, pid_rx=155667,
    8.     )
    1. [
    2. ...
    3. {'datetime': Timestamp('2022-08-01 14:05:17'), 'flag'True'fullName''src.cases_android.wy.test_health.TestMedicationReminder#test_edit_medication_reminder''status''passed''labels''微医APP_健康_用药提醒-修改用药提醒时间(@钟鑫)''start'1659333917269.0'stop'1659333926307.0'duration'9038.0'parameters': nan, 'statusDetails'"{'message': '', 'trace': ''}"'kano_url': nan, 'steps''1-点击一条用药提醒记录;1.1-点击「用药提醒_其中一条用药提醒」;2-选择必要时用药;2.1-获取元素的坐标;2.2-点击坐标「558.0」「640.5」所在的位置;2.3-点击坐标「558.0」「640.5」所在的位置;2.4-点击坐标「558.0」「640.5」所在的位置;2.5-从元素「用药提醒_用药时间-每隔几天用药」滑动到元素「用药提醒_用药时间-每隔几小时用药」的位置;2.6-点击「通用_完成」;3-点击保存;3.1-点击「通用_保存」;4-查看提醒时间为必要时;4.1-转换参数化元素;4.2-查看「用药提醒_用药时间」是否存在''desc'';system超过阈值;device_cpu_rate超过阈值;voltage超过阈值;voltage超过阈值;voltage超过阈值;'}
    4. ...
    5. ]

    绵薄之力

    最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走

    这些资料,对于想进阶【自动化测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。希望对大家有所帮助…

  • 相关阅读:
    从0到1构建基于自身业务的前端工具库
    Linux 内核参数:min_free_kbytes
    打造自己的前端组件库(奶妈版,超详细)
    Android 老开发被薪资竟然被应届生倒挂了……
    CentOS 7 部署Jellyfin详细教程
    霓虹【算法赛】蓝桥杯第7场强者挑战赛
    Kotlin高仿微信-第21篇-个人信息-修改头像
    代码随想录算法训练营 60天总结
    git基本操作
    网络代理技术的护航与网络安全
  • 原文地址:https://blog.csdn.net/jiangjunsss/article/details/126107810