目前有很多团队都在探索微信小程序的自动化测试,本文将对自动化测试过程中产生的数据进行进一步分析,得出相关的性能指标,为后续的性能优化提供数据支持。
1.自动化测试框架的选择
关于微信小程序测试框架的选择,我觉得只看是否使用腾讯TBS出品的测试框架。这个框架对于基于TBS内核的应用测试非常友好,但是这个框架的优势只停留在小程序早期版本,可以切换内核。通过实践发现,最新的小程序版本已经无法切换,所以直接框架并没有明显的优势。
转而选择业界比较流行的测试框架。目前业界的测试框架大致分为两类,一类是传统的自动化测试框架,比如阿里;一类是基于UI视觉的自动化测试框架,比如网易。两者实现上的区别如下:
对于传统的测试框架,实现基本都是依靠在客户端安装一个可以进行选择或者交互行为的APP,比如IOS端。而基于UI视觉的方案则是依靠STF框架中的工具,比如视频流、工具等,这些工具都是使用NDK开发的,只需要将对应的编译文件上传到客户端,在后台运行即可。
二者实际比较结果如下:
由于传统测试框架是基于CS框架,通讯交换耗时相对较长,基于UI 的底层框架,实时性高,结合高效的 Diff算法,能够快速响应。
2. 收集与监控框架设计
收集框架
整体的采集监控框架分为采集部分和统计分析部分。在采集部分,本方案作为自动化测试框架,结合实时快照流数据的采集。图中的小程序rpc可以将自动化脚本的运行环境和小程序的运行环境直接打通,并且可以提供更多的能力,比如元素选择等。在自动化脚本中,提供了小程序性能面板的控制,比如在QA流程案例中自动打开性能面板并导出文件,以及集成一些性能案例,比如每个页面的滚动操作等。整个QA流程案例完成后,会生成快照和数据并离线存储。在统计分析部分,先对离线文件的源数据进行处理,然后按照指定的性能指标进行计算,最后输出统计报表。
2. 数据收集
写完测试脚本之后,就可以进行流程测试了。除了借助常规的流程测试框架,比如断言库的帮助,我们还可以获取测试过程的视频。对于微信小程序,我们还可以将其特有的功能导出,可以在开发者工具的标签页中进行追踪展示。
2.1 数据类型
本解决方案采集的数据主要分为以下两部分:
两种数据类型2.1.1 快照数据
测试时的屏幕录制可以直观的反映测试结果,比如首屏的填充速度等,这里的快照数据是视频帧,也就是快照图片。
目前录屏方式有两种,一种是通过原生 API,比如调用原生方法,另一种是将实时传输的数据保存为图片文件。前者存在兼容性问题,在部分华为手机上无法调用,因此推荐的方式是将实时传输的流式数据保存为图片文件。
在官方的源码中,提供了将图像数据转换成的方法,有兴趣的可以自己去看看,我们只需要建立连接就可以实时接收流,并且进行控制,包括切换,保存等操作。
2.1.2 数据
小程序开发文档里已经介绍了如何打开性能面板和导出文件,这里要注意的是,只有手机端支持导出功能。
但对于自动化的需求,手动打开导出是远远不够的,需要设计一个自动化的流程来支撑。这里主要有两个步骤:第一,打开性能面板,最后导出文件;第二,从手机拉取文件,保存到本地后删除该文件(防止文件冗余和混乱)。
第一步的流程案例比较好写,只需要找到元素位置就可以了,而且打开开发版小程序是一次性的事情,只需要打开一次之后就不用再操作了。第二步就比较麻烦了,首先我们需要定位文件在手机里的存放位置,除了暴力手动搜索,我们还可以黑客入侵进入源码来查找,这里简单介绍一下。首先在开发者工具的data()中,执行之后会打开对应的目录,找到/目录,这个目录对应开发者工具的tab的接口函数,搜索.js文件、全局搜索等关键字,可以找到如下文件保存路径:
tencent/MicroMsg/appbrand/trace
显然这个路径是相对路径,需要获取系统存储路径才能完成。最后执行adb命令拉取并删除相应文件即可。具体代码片段如下:
const AdmZip = require('adm-zip')
const baseDir = await driver.execute('mobile:shell', {
command: 'echo',
args: ['$EXTERNAL_STORAGE']
})
const folder = `${baseDir.replace('\n', '')}/tencent/MicroMsg/appbrand/trace`
const folderBase64 = await driver.pullFolder(folder)
const buffer = Buffer.from(folderBase64, 'base64')
// 拉取的是压缩文件,需要解压缩const zip = new AdmZip(buffer)
for (const entry of zip.getEntries()) {
const decompressedData = zip.readFile(entry)
// 保存 fs.writeFileSync(./traceLog/${entry.entryName}, zip.readAsText(entry), 'utf8')
// 删除远端的文件 await driver.execute('mobile:shell', {
command: 'rm',
args: [${folder}/${entry.entryName}]
})
}
2.2 原始数据分析
至此,我们可以得到两组原始数据、快照数据(每秒6张以上,分布不均匀)和数据文件。接下来我们需要对原始数据做一些简单的处理和展示。
2.2.1 快照数据处理
快照数据处理主要有两个处理点:
A.时间戳对齐
我们将每一条完整的数据流都保存成一张图片,图片名称可以是保存的时间戳(),也方便后续处理。但是这里有一个问题,客户端时间戳和本地时间戳是有误差的,为了统一,应该以客户端时间戳为标准。所以在执行之前,我们需要计算两端的时间戳误差,方法如下:
const phoneTsStr = await driver.execute('mobile:shell', {
command: 'date',
args: ["'+%s%N'"]
})
const phoneTs = parseInt(phoneTsStr.substr(0, 13), 10)
const localTs = +new Date()
const offsetTs = localTs - phoneTs
注意:在某些系统上,date 不支持 %N。您可以安装其他插件来处理它。
B.关键帧提取
由于整个测试流程比较长,特别是使用方案的时候,小程序中大部分元素标签都是缺失的,只能通过漫长的方法才能获取,这个检索过程非常耗时,直接导致保存的快照数据量会非常大,在静止画面等待的时候,会有很多重复的图片。为了在保存原始数据的同时还能直观的展示UI变化点,需要提取关键图片。方法比较暴力,就是对每一帧和上一帧进行-diff操作,如果结果超过设定的阈值,则认为是关键帧。diff库有很多,下面介绍两个基于像素对比的图像diff库:
为了加快diff操作的速度,我们根据小程序的具体场景做了一些优化。
由于案例执行过程中,绩效面板处于打开状态,右上角会保留一块黑底的招牌,这部分位置和内容几乎不会发生改变,我们无需监控。另外小程序的布局多为从上到下、从左到右分布,基于此我们将diff区域切割为左侧的长条(宽度可根据实际情况设置),可以大大提高处理速度。
图像裁剪
至于阈值如何设定,需要根据内容的丰富程度以及多次实验的结果来制定。
2.2.2 数据处理
在小程序开发者工具中我们可以看到该文件的展示结果,通过追踪,我们在目录中找到了该文件,但是这个文件已经被处理过,并且删除了内容,因此我们参考微信官方的处理方案,解析出符合我们需求的内容。
原始文件以每行一条记录的方式保存,每行有 4 个模块(某些情况下可能缺少最后一个模块),每个模块之间以逗号分隔。如下图所示:
// 格式Name, Cat, Type, StartTime, EndTime, Args
// 例如ResourcePrepare, Native, X, 217, 602,
具体解释如下:

记录的顺序并不是按照从小到大排序的,而是按照父子方法的调用时间来排序的。我们来看下面的案例,这是小程序刚启动时的准备阶段的记录。结合官方的指标定义,展示如下片段:
X5Prepare,Native,X,216,217,
// 资源准备耗时,包括下载耗时(初次打开的情况下)和准备耗时ResourcePrepare,Native,X,217,602,
ActivityCreate,Native,X,0,1579,
WebViewInit+PageFrameLoad,Native,X,828,2018,
pageframe,Native,X,2533,2983,
WAWebview,Native,X,1942,2203,
...
App.onLaunch,App,X,4413,4620,
...
App.onShow,App,X,4621,4652,
...
Page.onLoad,pages/index/index,X,6069,6184,
// 首次渲染耗时firstRender,pages/index/index,X,6077,6168,
Page.onShow,pages/index/index,X,6189,6191,
...
// 页面切换耗时PageLoad,Native,X,1241,6218,
...
// 启动耗时startupDone,Native,X,0,6250,%7B+%22appMd5%22%3A+%22016f8777cbb7c3397cb2d61955e5a317%22+%7D
需要对上述源数据进行预处理,统计父子调用关系,整理调用时间点,具体统计处理脚本可以在这里找到。
3. 绩效指标制定
对上面的原始数据进行处理和统计整理后,我们可以得到如下的数据集:
3.1 小程序启动流程分析
在设置指标之前,需要分析小程序的启动流程,通过收集快照数据和日志,对比各个调用堆栈的时间点,得到下图的启动。
小程序启动加载时间轴
从官方文档中我们可以了解到,日志中的统计是性能面板上的启动时间,而在启动方法的最后,统一视图才刚刚开始。在这个过程中,经历了页面切换,初始渲染等操作。推测是系统中的创建过程,也就是启动微信浏览器的内核,是资源准备过程,如果是第一次进入,准备中还会包括资源下载过程。
+操作,紧接着的是and操作,从命名上看,就是and的初始化(小程序拥有原生体验的原因之一就是用到了技术)。在启动过程中,会有两个这样的过程,第一个是加载视图,第二个是准备home视图。
3.1 通用性能指标定义
这部分的指标可以统计到小程序每一页(以日志中的记录来拆分),前提是每一页的测试用例都会先进行一次来回滚动操作,以便获取滚动过程中的FPS和内存变化值。
两个页面的生命周期中page.和page.的区别。
参考性能面板,由文件中的类别提供。
和 之差为首次渲染时View和App的总执行时间,这个和性能面板上的首次渲染时间(View的渲染时间)是不一样的。
从日志中分析生命周期执行结束到第一次渲染结果的时间
当前页面上第一次wx.方法调用和page.的区别。请求数据到达越快,内容渲染越快。
统计当前小程序页面的平均和最大内存占用,内存占用过大在低端设备上可能会造成崩溃或者黑屏。
统计当前小程序页面内的平均和最大FPS,人眼静止时,画面可以保持连续,但低于30时,就会慢慢卡顿。
统计当前小程序页面中方法的调用频率(单位:秒),页面渲染过程中,如果频率过快,会影响渲染速度。
表示页面渲染的速度(单位:毫秒)。首先确定需要分析的记录时间段:1.首页,从开始到触发滚动前的结束;2.非首页,从页面切换开始(从当前页面开始到触发滚动前的结束)。统计这个时间段内每个快照的填充率,然后按照以下公式计算数值。
本文使用开源库来计算该值。
这里附上一张官方的示意图(来源),图中的统计数据单位是秒,白色柱状图代表每秒页面填充率以及对应的数值,可以看出数值越小代表内容填充越快。
3.2 首屏(小程序落地)绩效指标定义
小程序首次启动时会有一些特有的数据,因此可以单独统计一些性能指标
包含资源下载及加载时间,若非首次打开则无下载时间。
参考性能面板,由文件中的类别提供。
四、结论
本文主要介绍小程序自动化测试过程中,收集快照和日志的方法,并对收集到的数据进行筛选、统计,分析得到相关性能指标,为性能优化提供数据支撑。
原文来自知乎:作者:
其他建议
原来微信支付软件架构是这样的!!!
之前必读系列 - this//call 测试点
有了这个!你还在为不会写正则表达式而烦恼吗?
看看我如何将节点接口时间缩短了 23%!
你也是时候加入前端了!【轻松上手】
高级前端工程师如何高效部署前端应用?
4 如何帮您删除无效代码?终极树优化指南
高级前端工程师需要了解数据结构和算法
【撩妹教程】如何教会公司新来的女实习生什么是“结束”?
前端开发者如何在繁忙的业务中提升自己【合集系列】知识图谱-入门级培训,知识体系进阶巩固 全链路日志如何实现?大公司高性能小程序原来是这样炼成的!感觉未来几年可能改变封装方式!秒测应用开箱方案改进 你距离前端专家称号还有多远?
点开的人都很帅/漂亮