能用机器完成的,千万别堆工作量|持续集成中的性能自动化测试

简介: 作者:闲鱼技术-灯阳1.背景当前闲鱼在精益开发模式下,整个技术团队面临了诸多的能力落地和挑战,尤其是效能方面的2-1-1的目标(2周需求交付周期,1周需求开发周期,1小时达到发布标准),具体可见 闲鱼工程师是如何构建持续集成流水线,让研发效率翻倍的 ,在这个大目标下,就必须把每个环节都做到极致。

作者:闲鱼技术-灯阳

1.背景

当前闲鱼在精益开发模式下,整个技术团队面临了诸多的能力落地和挑战,尤其是效能方面的2-1-1的目标(2周需求交付周期,1周需求开发周期,1小时达到发布标准),具体可见 闲鱼工程师是如何构建持续集成流水线,让研发效率翻倍的 ,在这个大目标下,就必须把每个环节都做到极致。自动化的建设是决定CI成败的关键能力,今天分享一下闲鱼Android客户端性能自动化环节的实践。

2.面临的问题

2.1 主要是两个方面的问题

  • 工具缺失:

目前淘宝系,对于线上性能水位的监控有一套完善的体系,但是针对新功能的性能测试,每个业务团队都有对应的性能专项小组,产出的工具都是根据自己业务特点的定制开发的,闲鱼客户端目前使用Flutter做为客户端主开发语言,对于Flutter性能数据的获取及UI自动化测试支撑工具目前是缺失的,同时业界对Flutter自动化和性能相关的实践几乎没有;

  • 测试工作量翻N倍(N=一个版本周期内的分支数):

原先的开发模式是功能测试集成测试一起进行的,所以性能测试只需要针对集成后的包进行测试即可,到现在转变为泳道的开发模式,一个版本内会一般包含十几个左右的泳道分支甚至更多,我们必须确保每个泳道的分支的性能是达标的,如果有性能问题需要第一时间反馈出来,如果遗留到集成阶段,问题的排查(十几个分支中筛查),问题的解决将会耗费大量的时间,效率很难得到大的提升;

2.2 问题思考

体系化解决,要让每个泳道分支都得到有效测试覆盖,测试件能够自动化执行,持续反馈结果

img

3. 解决方案

综合上述问题,梳理如下解决方案:

  • 针对Flutter性能数据的获取(比如,Flutter有自己的SurfaceView,原有Native计算FPS的方式无法直接使用)
  • 针对Flutter UI自动化的实现(Flutter/Native UI混合栈的处理)
  • 性能自动化脚本 / 性能数据自动采集、上报 融入CI流程
  • 性能问题的通知 / 报表展示 / 分析

3.1 性能数据

[FPS]

解析处理 adb shell dumpsys SurfaceFlinger --latency 的数据,详细请见文末参考链接(该方式兼容Flutter及Native的解决方案,已在Android4.x-9.x验证可行),处理SurfaceFlinger核心代码如下:

dumpsys SurfaceFlinger --latency-clear
#echo "dumpsys SurfaceFlinger..."
if [[ $isflutter = 0 ]];then
  window=`dumpsys window windows | grep mCurrent | $bb awk '{print $3}'|$bb tr -d '}'` # Get the current window
  echo $window
fi
if [[ $isflutter = 1 ]];then
  window=`dumpsys SurfaceFlinger --list |grep '^SurfaceView'|$bb awk 'NR==1{print $0}'`
if [ -z "$window" ]; then 
  window="SurfaceView"
fi
echo $window
fi
$bb usleep $sleep_t
dumpsys SurfaceFlinger --latency "$window"|$bb awk -v time=$uptime -v target=$target -v kpi=$KPI '{if(NR==1){r=$1/1000000;if(r<0)r=$1/1000;b=0;n=0;w=1}else{if(n>0&&$0=="")O=1;if(NF==3&&$2!=0&&$2!=9223372036854775807){x=($3-$1)/1000000/r;if(b==0){b=$2;n=1;d=0;D=0;if(x<=1)C=r;if(x>1){d+=1;C=int(x)*r;if(x%1>0)C+=r};if(x>2)D+=1;m=r;o=0}else{c=($2-b)/1000000;if(c>1000){O=1}else{n+=1;if(c>=r){C+=c;if(c>kpi)o+=1;if(c>=m)m=c;if(x>1)d+=1;if(x>2)D+=1;b=$2}else{C+=r;b=sprintf("%.0f",b+r*1000000)}}};if(n==1)s=sprintf("%.3f",$2/1000000000)};if(n>0&&O==1){O=0;if(n==1)t=sprintf("%.3f",s+C/1000);else t=sprintf("%.3f",b/1000000000);T=strftime("%F %T",time+t)"."sprintf("%.0f",(time+t)%1*1000);f=sprintf("%.2f",n*1000/C);m=sprintf("%.0f",m);g=f/target;if(g>1)g=1;h=kpi/m;if(h>1)h=1;e=sprintf("%.2f",g*60+h*20+(1-o/n)*20);print s","t","T","f+0","n","d","D","m","o","e","w;n=0;if($0==""){b=0;w+=1}else{b=$2;n=1;d=0;D=0;if(x<=1)C=r;if(x>1){d+=1;C=int(x)*r;if(x%1>0)C+=r};if(x>2)D+=1;m=r;o=0}}}}' >>$file

[CPU]

使用的是top的命令获取(该方式获取性能数据时,数据收集带来的损耗最少)

export bb="/data/local/tmp/busybox"
$bb top -b -n 1|$bb awk 'NR==4{print NF-1}'

[内存]

解析 dumpsys meminfo $package 拿到 Java Heap,Java Heap Average,Java Heap Peak,Native Heap,Native Heap Average,Native Heap Peak,Graphics,Unknown,Pss 数据

do_statistics() {
    ((COUNT+=1))
    isExist="$(echo $OUTPUT | grep "Dalvik Heap")" 
    if [[ ! -n $isExist ]] ; then
        old_dumpsys=true
    else
        old_dumpsys=false
    fi
    if [[ $old_dumpsys = true ]] ; then
        java_heap="$(echo "$OUTPUT" | grep "Dalvik" | $bb awk '{print $6}' | $bb tr -d '\r')"
    else
        java_heap="$(echo "$OUTPUT" | grep "Dalvik Heap[^:]" | $bb awk '{print $8}' | $bb tr -d '\r')"
    fi
    echo "1."$JAVA_HEAP_TOTAL "2."$java_heap "3."$JAVA_HEAP_TOTAL
    ((JAVA_HEAP_TOTAL+=java_heap))
    ((JAVA_HEAP_AVG=JAVA_HEAP_TOTAL/COUNT))
    if [[ $java_heap -gt $JAVA_HEAP_PEAK ]] ; then
        JAVA_HEAP_PEAK=$java_heap
    fi
    if [[ $old_dumpsys = true ]] ; then
        native_heap="$(echo "$OUTPUT" | grep "Native" | $bb awk '{print $6}' | $bb tr -d '\r')"
    else
        native_heap="$(echo "$OUTPUT" | grep "Native Heap[^:]" | $bb awk '{print $8}' | $bb tr -d '\r' | $bb tr -d '\n')"
    fi
    ((NATIVE_HEAP_TOTAL+=native_heap))
    ((NATIVE_HEAP_AVG=NATIVE_HEAP_TOTAL/COUNT))
    if [[ $native_heap -gt $NATIVE_HEAP_PEAK ]] ; then
        NATIVE_HEAP_PEAK=$native_heap
    fi
    g_Str="Graphics"
    if [[ $OUTPUT == *$g_Str* ]] ; then
        echo "Found Graphics..."
        Graphics="$(echo "$OUTPUT" | grep "Graphics" | $bb awk '{print $2}' | $bb tr -d '\r')"
    else
        echo "Not Found Graphics..."
        Graphics=0
    fi
    Unknown="$(echo "$OUTPUT" | grep "Unknown" | $bb awk '{print $2}' | $bb tr -d '\r')"
    total="$(echo "$OUTPUT" | grep "TOTAL"|$bb head -1| $bb awk '{print $2}' | $bb tr -d '\r')"
}

[流量]

通过 dumpsys package packages 解析出当前待测试包来获取流量信息

uid="$(dumpsys package packages|$bb grep -E "Package |userId"|$bb awk -v OFS=" " '{if($1=="Package"){P=substr($2,2,length($2)-2)}else{if(substr($1,1,6)=="userId")print P,substr($1,8,length($1)-7)}}'|grep $package|$bb awk '{print $2}')"
echo "Net:"$uid
initreceive=`$bb awk -v OFS=" " 'NR>1{if($2=="wlan0"){wr[$4]+=$6;wt[$4]+=$8}else{if($2=="rmnet0"){rr[$4]+=$6;rt[$4]+=$8}}}END{for(i in wr){print i,wr[i]/1000,wt[i]/1000,"wifi"};for(i in rr){print i,rr[i]/1000,rt[i]/1000,"data"}}' /proc/net/xt_qtaguid/stats | grep $uid|$bb awk '{print $2}'`
inittransmit=`$bb awk -v OFS=" " 'NR>1{if($2=="wlan0"){wr[$4]+=$6;wt[$4]+=$8}else{if($2=="rmnet0"){rr[$4]+=$6;rt[$4]+=$8}}}END{for(i in wr){print i,wr[i]/1000,wt[i]/1000,"wifi"};for(i in rr){print i,rr[i]/1000,rt[i]/1000,"data"}}' /proc/net/xt_qtaguid/stats | grep $uid|$bb awk '{print $3}'`

echo "initnetarray"$initreceive","$inittransmit
getnet(){
    local data_t=`date +%Y/%m/%d" "%H:%M:%S`
    netdetail=`$bb awk -v OFS=, -v initreceive=$initreceive -v inittransmit=$inittransmit -v datat="$data_t" 'NR>1{if($2=="wlan0"){wr[$4]+=$6;wt[$4]+=$8}else{if($2=="rmnet0"){rr[$4]+=$6;rt[$4]+=$8}}}END{for(i in wr){print datat,i,wr[i]/1000-initreceive,wt[i]/1000-inittransmit,"wifi"};for(i in rr){print datat,i,rr[i]/1000-initreceive,rt[i]/1000-inittransmit,"data"}}' /proc/net/xt_qtaguid/stats | grep $uid`
    echo $netdetail>>$filenet
}

3.2 性能自动化脚本

  • 基于Appium的自动化用例,这个技术业界已经有非常多的实践了,这里我不再累述,如果不了解的同学,可以到Appium官网 http://appium.io
  • Flutter和Native页面切换使用App内的Schema跳转
  • Flutter页面的文本输入等交互性较强的场景使用基于Flutter框架带的Integration Test来操作

An integration test

Generally, an integration test runs on a real device or an OS emulator, such as iOS Simulator or Android Emulator. The app under test is typically isolated from the test driver code to avoid skewing the results.

Flutter的UI自动化及Flutter/Native混合页面的处理在测试上的应用后续单独开文章介绍,原理相关可以先参考 千人千面录制回放技术

3.3 性能自动化CI流程

img

3.4 性能数据报表

FPS相关

img

  • Framediff: 绘制帧的开始时间和结束时间差
  • FPS: 每秒展示的帧数
  • Frames: 一个刷新周期内所有的帧
  • jank: 一帧开始绘制到结束超过16.67ms 就记一次jank,jank非零代表硬件绘制掉帧,和屏幕硬件性能及相关驱 动性能有关
  • jank2: 一帧开始绘制到结束超过33.34ms 就记一次jank2
  • MFS: 在一个刷新周期内单帧最大耗时(每两行垂直同步的时间差代表两帧绘制的帧间隔)
  • OKT: 在一个刷新周期内,帧耗时超过16.67ms的次数
  • SS: 流畅度,通过FPS,MFS,OKT计算出来,流畅度 = 实际帧率比目标帧率比值60【目标帧率越高越好】 + 目标时间和两帧时间差比值20【两帧时间差越低越好】 + (1-超过16ms次数/帧数)*20【次数越少越好】

CPU

img

Memory

img

4. 成果展示

4.1 指定泳道分支性能监控

泳道分支出现了性能问题再报表上一目了然

img

4.2 性能专项支撑

1、Flutter商品详情页重构 14轮测试

img

2、客户端图片统一资源测试 4轮测试

img

5. 总结

性能自动化只是整个CI流程中的一个环节,为了极致效率的大目标,闲鱼质量团队还产出了很多支撑工具,CI平台,遍历测试,AI错误识别,用例自动生成等等,后续也会分享给大家。

6. 参考

https://testerhome.com/topics/2232
https://testerhome.com/topics/4775

相关文章
|
12天前
|
机器学习/深度学习 人工智能 jenkins
探索软件测试中的自动化与持续集成
【10月更文挑战第21天】 在软件开发的生命周期中,软件测试扮演着至关重要的角色。随着技术的进步和开发模式的转变,自动化测试和持续集成已经成为提高软件质量和效率的关键手段。本文将深入探讨自动化测试和持续集成的概念、实施策略以及它们如何相互配合以优化软件开发流程。我们将通过分析实际案例,展示这些技术如何在实际项目中发挥作用,以及面临的挑战和解决方案。此外,文章还将讨论未来趋势,包括人工智能在测试领域的应用前景。
62 17
|
25天前
|
jenkins 测试技术 持续交付
软件测试中的自动化与持续集成
在现代软件开发过程中,自动化测试和持续集成已成为不可或缺的组成部分。本文将深入探讨自动化测试和持续集成的重要性、优势以及如何有效实施它们以提升软件质量和开发效率。通过具体案例分析,我们将展示这些技术如何在实际项目中发挥作用,并讨论其面临的挑战及应对策略。
48 3
|
1月前
|
jenkins 测试技术 持续交付
探索自动化测试在持续集成中的应用与挑战
本文深入探讨了自动化测试在现代软件开发流程,特别是持续集成(CI)环境中的关键作用。通过分析自动化测试的优势、实施策略以及面临的主要挑战,旨在为开发团队提供实用的指导和建议。文章不仅概述了自动化测试的基本原理和最佳实践,还详细讨论了如何克服实施过程中遇到的技术难题和管理障碍,以实现更高效、更可靠的软件交付。
|
1月前
|
jenkins 机器人 测试技术
自动化与持续集成
自动化和持续集成(CI)是现代软件开发的重要实践。自动化通过技术手段执行任务,减少人工干预,提升效率和质量。持续集成则强调频繁将代码变更集成至共享库,伴随自动化构建和测试,旨在早期发现问题,加速迭代,提高代码质量。常见CI工具有Jenkins、Travis CI等,支持多语言和框架,促进高效协作与快速反馈。
|
1月前
|
监控 安全 测试技术
在实施自动化和持续集成的过程中,如何确保代码的安全性和合规性
在自动化和持续集成中,确保代码安全与合规至关重要。措施包括集成自动化安全工具、执行自动化合规检查、进行代码质量与安全检测、评估开源代码安全、实施基础设施即代码的安全标准、采用多层防御策略、加强安全教育与文化建设、使用合规性检测工具及许可证合规分析等,共同提升代码安全性与合规水平。
|
1月前
|
监控 测试技术 持续交付
自动化和持续集成在软件开发中各自扮演什么角色
在软件开发中,自动化与持续集成(CI)相辅相成,共同提升开发效率和软件质量。自动化通过高效执行测试、构建和部署等重复任务,减少人为错误,确保流程一致性,并支持持续部署。CI则通过频繁集成代码变更、提供快速反馈、提高代码质量和促进团队协作,简化部署过程。两者结合,极大优化了软件开发流程。
|
1月前
|
监控 jenkins 测试技术
探索软件测试中的自动化与持续集成####
本文旨在探讨软件测试中自动化测试与持续集成(CI)的融合实践,分析其对提升软件开发效率和质量的重要性。通过深入剖析自动化测试的优势、持续集成的核心概念以及两者结合的最佳实践案例,揭示这一技术趋势如何重塑现代软件开发流程。文章还将讨论实施过程中的挑战和应对策略,为读者提供一套实用的方法论指导。 ####
|
1月前
|
Devops jenkins 测试技术
DevOps实践:自动化部署与持续集成的融合之旅
【10月更文挑战第41天】在软件开发的世界中,快速迭代和高效交付是企业竞争力的关键。本文将带你走进DevOps的核心实践——自动化部署与持续集成,揭示如何通过它们提升开发流程的效率与质量。我们将从DevOps的基本理念出发,逐步深入到具体的技术实现,最终展示一个实际的代码示例,让理论与实践相结合,为你的开发旅程提供清晰的指引。
53 4
|
29天前
|
Devops 测试技术 持续交付
软件测试中的自动化与持续集成:最佳实践与挑战
在快速迭代的软件开发周期中,自动化测试和持续集成(CI)已成为提高软件质量和加速产品上市的关键策略。本文探讨了自动化测试和CI的实施如何帮助开发团队提前发现缺陷、缩短反馈循环,并确保代码质量。我们将深入分析自动化测试的策略选择、工具应用以及面临的挑战,同时提供一些克服这些挑战的最佳实践。
47 0