赞
踩
开发找到我说:“反复进入阅读器存在内存泄漏最终导致内存溢出(out of memory,即 oom问题)你帮忙测试一下”。听到此需求有两点要解决,一个是长时间反复进入阅读器,一个是性能数据监控。测试时间有限,不可能一直手动翻页去看书,效率低不说,翻的手指头疼。内存怎么看呢?市面上有很多工具,其中prefdog是业界相对来说做的最好的,但是随着prefdog的收费,长时间的性能测试花费较大,于是就有了本篇文章的诞生。
本文主要实现通过monkey脚本和Airtest进行自动化重复操作,使用Python通过adb命令获取数据信息,通过PyQt5创建图形化界面,在PyQtGraph中实时显示数据,Excel保存数据。
两种实现APP自动化的方式
一、monkey脚本:
1.monkey简介
Monkey 是一个在您的模拟器或设备上运行的程序,它生成用户事件的伪随机流,例如点击、触摸或手势,以及许多系统级事件。您可以使用 Monkey 以随机但可重复的方式对您正在开发的应用程序进行压力测试
2.monkey自动化脚本命令
有关monkey事件、参数、日志管理网上有很多文章可参考,此文主要介绍monkey脚本中的命令。monkeyscript是monkey的脚本语言,能够被monkey识别的命令集合,可以实现一些固定的重复性动作。Monkey可以通过命令加载脚本来进行测试,简单方便。
脚本命令
LaunchActivity(pkg_name, cl_name): 启动应用,pkg_name包名,cl_name启动的activity名
DispatchPress(keycode): 系统按键,例如home键,back键;
UserWait:等待
Tap(x, y, tapDuration):模拟一次手指单击事件,x,y:坐标值,tapDuration:可选项,表示单击的持续时间
Drag(xStart, yStart, xEnd, yEnd) :在屏幕上滑动,坐标是从哪一点滑到哪一点
LongPress(): 长按2s
ProfileWait(): 等待5s
PressAndHold(x, y, pressDuration) :模拟长按
PinchZoom(x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End, stepCount): 模拟缩放
DispatchString(input): 输入字符串
RunCmd(cmd) :执行shell命令,比如截图 screencap -p /data/local/tmp/tmp.png
DispatchFlip(true/false) :打开或者关闭软键盘
DeviceWakeUp() :唤醒屏幕
示例反复进入阅读器脚本read.txt
- #头文件信息
- type=raw events
- count=10
- speed=1.0
- start data >>
- #具体的脚本内容
- LaunchActivity(org.geometerplus.android.fbreader.FBReader)
- UserWait(1000)
- Tap(436,1425)
- UserWait(1000)
- DispatchPress(KEYCODE_BACK)
3.执行脚本
a. 编写脚本read.txt
b. 将脚本上传入手机
adb push D:\read.txt(电脑中的位置) /sdcard(手机中的位置)
c. 执行脚本
adb shell monkey -f sdcard/read.txt(手机中的位置) 1000(次数)
二、Airtest
1.Airtest的介绍
Airtest是一个跨平台的、基于图像识别的UI自动化测试框架,适用于游戏和App测试,支持平台有Windows、Android和iOS平台,原理:利用截图的方式,在已展示出的手机界面中寻找所匹配的图片。
有关Airtest安装、环境配置、连接移动设备,官网都有详细说明https://airtest.doc.io.netease.com/
2.Airtest中的事件介绍
在Airtest辅助窗悬停可点到事件参数的具体介绍和用法。
Airtest事件
touch:触摸
swipe:滑动
exists:存在什么样的元素
keyevent:手机按键点击,比如:HOME,BACK
text:输入文本
snapshot:截图功能
sleep:延迟几秒
assert_exists:断言截图是否存在于当前页面,后面文案是测试点
assert_not_exists:断言截图内容不存在与当前这个页面,后面文案是测试点
assert_equal:断言first和second是否相等,相等就通过
assert_not_equal:断言first和second是否相等,不相等就会通过
wait:超时
示例反复进入阅读器脚本read.air
- # -*- encoding=utf8 -*-
- __author__ = "admin"
-
- from airtest.core.api import *
- import datetime
- auto_setup(__file__)
-
- def repeated_entry_reader(num):
- """反复进入阅读器"""
- # 开始时间
- start_time = datetime.datetime.now()
- for i in range(num):
- # 点击进入阅读器
- touch((400, 1200),duration=0.05)
- # 返回
- keyevent("BACK")
- # 当前时间
- over_time = datetime.datetime.now()
- # 已运行时间
- total_time = (over_time-start_time).total_seconds()
- # 转化成min,保留2位小数
- total_time_min = round(total_time/60, 2)
- print("点击%s次"%(i+1),"剩余%s次"%(num-i-1))
- print('已运行%s分钟' % total_time_min)
-
- if __name__ == '__main__':
- repeated_entry_reader(10)

性能数据动态展示
1.介绍
Android性能数据动态展示工具,可以绘制CPU、Memory、FPS信息,并保存数据。
2.软件架构
工具python3.8编写
CPU、Memory、FPS信息是通过adb命令获得出的数据来解析的
GUI界面PyQt5创建图形化界面,在PyQtGraph中实时显示数据
通过Excel保存数据
3.安装教程
a. 安装python3.8,官网下载地址https://www.python.org/downloads/
b. 安装PyQt5
pip install PyQt5
c. 安装PyQtGraph
pip install PyQtGraph
d.安装其他依赖库
4.实现代码monitoringdata.py
- from PyQt5 import QtWidgets, QtCore, QtGui
- import pyqtgraph as pg
- import sys
- import traceback
- import csv, os, time, math
-
- class MonitoringData(QtWidgets.QMainWindow):
- def __init__(self, pkg, device_name):
- super().__init__()
- self.pkg = pkg # 包名
- self.device_name = device_name # 设备名
- self.timer_interval(2000) # 数据刷新时间间隔
- self.data_list = []
- self.cpu_data = []
- self.memory_data = []
- self.fps_data = []
- self.cpucsvfile = open('./CPU_' + time.strftime("%Y_%m_%d_%H_%M_%S") + '.csv', 'w', encoding='utf8', newline='')
- self.save_data('cpu', [('timestamp', 'CPU(%)')]) # 定义cpu数据列表title
- self.memcsvfile = open('./Memory_' + time.strftime("%Y_%m_%d_%H_%M_%S") + '.csv', 'w', encoding='utf8', newline='')
- self.save_data('mem', [('timestamp', 'Memory(MB)')]) # 定义Memory数据列表title
- self.fpscsvfile = open('./FPS_' + time.strftime("%Y_%m_%d_%H_%M_%S") + '.csv', 'w', encoding='utf8', newline='')
- self.save_data('fps', [('timestamp', 'FPS')]) # 定义FPS数据列表title
- # 创建监控窗口
- self.setWindowTitle("App性能数据显示")
- self.App_monitoring_data = QtWidgets.QWidget() # 创建一个主部件
- self.setCentralWidget(self.App_monitoring_data) # 设置窗口默认部件
- self.resize(800, 800) # 设置窗口大小
- # 创建cpu监控图像
- self.cpu_image = QtWidgets.QGridLayout() # 创建cpu网格布局
- self.App_monitoring_data.setLayout(self.cpu_image) # 设置cpu的主部件为网格
- self.cpu_plot_widget = QtWidgets.QWidget() # cpu的widget部件作为K线图部件
- self.plot_layout = QtWidgets.QGridLayout() # cpu的网格布局层
- self.cpu_plot_widget.setLayout(self.plot_layout) # 设置K线图部件的布局层
- self.cpu_plot_plt = pg.PlotWidget(title='CPU', left='CPU(%)') # cpu的绘图部件
- self.cpu_plot_plt.showGrid(x=True, y=True) # 显示cpu图形
- self.plot_layout.addWidget(self.cpu_plot_plt) # 添加绘图部件到K线图部件的网格布局层
- self.cpu_image.addWidget(self.cpu_plot_widget, 2, 0, 3, 3) # 将上述部件添加到布局层中
- self.cpu_plot_plt.setYRange(max=120, min=0) # 设置cpu的纵坐标范围
- # 创建Memory监控图像
- self.mem_image = QtWidgets.QGridLayout() # 创建memory网格布局
- self.App_monitoring_data.setLayout(self.mem_image) # 设置memory主部件的布局为网格
- self.mem_plot_widget = QtWidgets.QWidget() # memory的widget部件作为K线图部件
- self.mem_plot_layout = QtWidgets.QGridLayout() # memory的网格布局层
- self.mem_plot_widget.setLayout(self.mem_plot_layout) # 设置K线图部件的布局层
- self.mem_plot_plt = pg.PlotWidget(title='Memory', left='Pss Total(MB)') # memory绘图部件
- self.mem_plot_plt.showGrid(x=True, y=True) # 显示memory图形
- self.plot_layout.addWidget(self.mem_plot_plt) # 添加绘图部件到K线图部件的网格布局层
- self.mem_image.addWidget(self.mem_plot_widget, 1, 0, 3, 3) # 将上述部件添加到布局层中
- self.mem_plot_plt.setYRange(max=600, min=0) # 设置memory的纵坐标范围
- # 创建FPS监控图像
- self.fps_image = QtWidgets.QGridLayout() # 创建fps网格布局
- self.App_monitoring_data.setLayout(self.fps_image) # 设置fps主部件的布局为网格
- self.fps_plot_widget = QtWidgets.QWidget() # fps的widget部件作为K线图部件
- self.fps_plot_layout = QtWidgets.QGridLayout() # fps的网格布局层
- self.fps_plot_widget.setLayout(self.fps_plot_layout) # 设置K线图部件的布局层
- self.fps_plot_plt = pg.PlotWidget(title='FPS', left='FPS') # fps绘图部件
- self.fps_plot_plt.showGrid(x=True, y=True) # 显示fps图形网格
- self.plot_layout.addWidget(self.fps_plot_plt) # 添加绘图部件到K线图部件的网格布局层
- self.fps_image.addWidget(self.fps_plot_widget, 1, 0, 3, 3) # 将上述部件添加到布局层中
- self.fps_plot_plt.setYRange(max=70, min=0) # 设置fps的纵坐标范围
-
- def timer_interval(self, timeinterval):
- """启动定时器 时间间隔秒"""
- self.timer = QtCore.QTimer(self)
- self.timer.timeout.connect(self.get_cpu_info)
- self.timer.timeout.connect(self.get_memory_info)
- self.timer.timeout.connect(self.get_fps_info)
- self.timer.start(timeinterval)
-
- def get_current_time(self):
- """获取当前时间"""
- currenttime = time.strftime("%H:%M:%S", time.localtime())
- return currenttime
-
- def get_cpu_info(self):
- """获取cpu数据"""
- try:
- result = os.popen("adb -s {} shell dumpsys cpuinfo | findstr {}".format(self.device_name, self.pkg))
- # result = os.popen("adb -s {} shell top -m 100 -n 1 -d 1 | findstr {}".format(self.device_name, self.pkg)) # 执行adb命令
- res = result.readline().split(" ") # 将获取的行数据使用空格进行分割
- if res == ['']: # 处理没有数据的情况
- print('no data')
- pass
- else:
- cpuvalue1 = list(filter(None, res))[2] # 获取cpu
- cpuvalue = cpuvalue1.strip('%') # 去除%号
- current_time = self.get_current_time()
- if cpuvalue == 'R': # 过滤cpu等于R
- pass
- else:
- cpu = float(cpuvalue)
- print("CPU:", cpu)
- self.save_data('cpu', [(current_time, cpuvalue)]) # 将数据保存到Excel
- self.data_list.append(cpu) # 将数据写入列表
- self.cpu_plot_plt.plot().setData(self.data_list, pen='g') # 将数据载入图像中
- except Exception as e:
- print(traceback.print_exc())
-
- def get_memory_info(self):
- """获取Memory数据"""
- try:
- result = os.popen("adb -s {} shell dumpsys meminfo {}".format(self.device_name, self.pkg)) # 执行adb命令
- res = result.readlines()
- for line in res:
- if "TOTAL:" in line: # 不同手机adb shell dumpsys meminfo packagename 获取的Pss Total 不同,有的手机是TOTAL:,有的是TOTAL PSS:,这里做了一下兼容
- pss_total1 = line.split(" ")[18] # 将获取的行数据使用空格进行分割并取出第 18个元素
- elif 'TOTAL PSS:' in line:
- pss_total1 = line.split(" ")[15] # 将获取的行数据使用空格进行分割并取出第 15个元素
- else:
- continue
- pss_total = round(float(pss_total1) / 1024, 2) # 单位换算成MB,保留2位小数
- current_time = self.get_current_time()
- print("Memory:", pss_total)
- self.save_data('mem', [(current_time, pss_total)]) # 将数据保存到Excel
- self.memory_data.append(pss_total) # 将数据加入列表
- self.mem_plot_plt.plot().setData(self.memory_data, pen='y') # 将数据载入图像中
- except Exception as e:
- print(traceback.print_exc())
-
- def get_fps_info(self):
- """获取fps数据"""
- try:
- result = os.popen("adb -s {} shell dumpsys gfxinfo {}".format(self.device_name, self.pkg)) # 执行adb命令
- res = result.readlines() # 获取所有行数据
- frame_count = 0 # 定义frame_count初始值
- vsync_overtime_s = [] # 定义vsync_overtime_s列表
- jank_num = 0 # 定义jank_num初始值
- for line in res: # 循环行
- if '\t' in line: # 取出带\t的所有行
- if '\tcom.kmxs.reader' in line: # 过滤\tcom.kmxs.reader数据
- r = False
- elif '\tDraw' in line: # 过滤\tDraw数据
- r = False
- elif '/android.view' in line:
- r = False
- else:
- frame_count = frame_count + 1 # 循环次数
- fps = line.split('\t') # 分离数据
- # print(fps)
- Draw = float(fps[1]) # 取数据
- Prepare = float(fps[2]) # 取数据
- Process = float(fps[3]) # 取数据
- Execute = float(fps[4].replace('\n', '')) # 取数据
- render_time = Draw + Prepare + Process + Execute # 计算render_time
- # print(render_time)
- # print('Native Heap is ', Native_Heap_mem)
- if render_time > 16.67: # 大于16.67认为是一次卡顿
- jank_num += 1 # 计算卡顿次数
- vsync_overtime = math.ceil(render_time / 16.67) - 1 # 向上取整
- vsync_overtime_s.append(vsync_overtime) # 添加到列表
- else:
- continue
-
- vsync_overtime_sum = sum(vsync_overtime_s) # 计算列表中所有数据的和
- fps_sum = frame_count + vsync_overtime_sum
- if fps_sum == 0:
- fps = 0
- print("手机屏幕静止")
- else:
- fps = round(frame_count * 60 / fps_sum, 2) # 计算fps,并保留2位小数
- current_time = self.get_current_time()
- self.save_data('fps', [(current_time, fps)]) # 将数据保存到Excel
- print("FPS:", fps)
- self.fps_data.append(fps) # 将数据加入列表
- self.fps_plot_plt.plot().setData(self.fps_data, pen='m') # 将数据载入图像中
- except Exception as e:
- print(traceback.print_exc())
-
- def save_data(self, data_type, cpudata):
- """保存数据到Excel"""
- if data_type == 'cpu':
- writer = csv.writer(self.cpucsvfile) # 写入Excel
- writer.writerows(cpudata) # 将数据写入Excel
- elif data_type == 'mem':
- writer = csv.writer(self.memcsvfile)
- writer.writerows(cpudata)
- elif data_type == 'fps':
- writer = csv.writer(self.fpscsvfile)
- writer.writerows(cpudata)
- else:
- print('data_type error!')
- if __name__ == '__main__':
- app = QtWidgets.QApplication(sys.argv)
- data = MonitoringData('', '') # 请修改包名和设备号
- data.show()
- sys.exit(app.exec_())

5.最终结果
使用说明
a. 连接android设备,通过adb devices获取设备号
b. 修改包名和设备号
c. UI自动化脚本运行起来后,直接运行monitoringdata.py脚本
小贴士
1.Android设备x,y坐标可打开系统设置-->开发者模式-->指针位置查看。
2.性能监控无法获取FPS时,则很可能是手机的“GPU呈现模式分析”未打开,在手机的开发者选项中,找到“GPU呈现模式分析”,选择“在adb shell dumpsys gfxinfo中”,如果是华为手机,则选择“在屏幕上显示为线型图”。
参考文献:
1.https://www.cnblogs.com/zeliangzhang/p/15268578.html
2.https://airtest.doc.io.netease.com/
3.https://developer.android.com/studio/test/monkeyrunner/MonkeyDevice
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。